mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
@@ -40,6 +40,8 @@ add_library(yul
|
||||
Dialect.cpp
|
||||
Dialect.h
|
||||
Exceptions.h
|
||||
FunctionReferenceResolver.cpp
|
||||
FunctionReferenceResolver.h
|
||||
Object.cpp
|
||||
Object.h
|
||||
ObjectParser.cpp
|
||||
@@ -140,8 +142,6 @@ add_library(yul
|
||||
optimiser/FullInliner.h
|
||||
optimiser/FunctionCallFinder.cpp
|
||||
optimiser/FunctionCallFinder.h
|
||||
optimiser/FunctionDefinitionCollector.cpp
|
||||
optimiser/FunctionDefinitionCollector.h
|
||||
optimiser/FunctionGrouper.cpp
|
||||
optimiser/FunctionGrouper.h
|
||||
optimiser/FunctionHoister.cpp
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
|
||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/FunctionReferenceResolver.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
@@ -37,16 +36,15 @@ using namespace solidity::yul;
|
||||
|
||||
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
|
||||
{
|
||||
for (auto const& statement: _ast.statements)
|
||||
if (auto const* function = get_if<FunctionDefinition>(&statement))
|
||||
(*this)(*function);
|
||||
m_currentNode = newNode();
|
||||
(*this)(_ast);
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
|
||||
{
|
||||
walkVector(_functionCall.arguments | ranges::views::reverse);
|
||||
newConnectedNode();
|
||||
m_currentNode->functionCall = _functionCall.functionName.name;
|
||||
m_currentNode->functionCall = &_functionCall;
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(If const& _if)
|
||||
@@ -80,7 +78,9 @@ void ControlFlowBuilder::operator()(Switch const& _switch)
|
||||
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
|
||||
{
|
||||
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
|
||||
yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used.");
|
||||
ScopedSaveAndRestore leave(m_leave, nullptr);
|
||||
ScopedSaveAndRestore _break(m_break, nullptr);
|
||||
ScopedSaveAndRestore _continue(m_continue, nullptr);
|
||||
|
||||
FunctionFlow flow;
|
||||
flow.exit = newNode();
|
||||
@@ -92,7 +92,7 @@ void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
|
||||
|
||||
m_currentNode->successors.emplace_back(flow.exit);
|
||||
|
||||
m_functionFlows[_function.name] = move(flow);
|
||||
m_functionFlows[&_function] = move(flow);
|
||||
|
||||
m_leave = nullptr;
|
||||
}
|
||||
@@ -166,14 +166,17 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
|
||||
Block const& _ast
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_cfgBuilder(_ast)
|
||||
m_cfgBuilder(_ast),
|
||||
m_functionReferences(FunctionReferenceResolver{_ast}.references())
|
||||
{
|
||||
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
|
||||
for (auto&& [function, flow]: m_cfgBuilder.functionFlows())
|
||||
{
|
||||
yulAssert(!flow.entry->functionCall);
|
||||
m_processedNodes[name] = {};
|
||||
m_pendingNodes[name].push_front(flow.entry);
|
||||
m_functionSideEffects[name] = {false, false, false};
|
||||
yulAssert(function);
|
||||
m_processedNodes[function] = {};
|
||||
m_pendingNodes[function].push_front(flow.entry);
|
||||
m_functionSideEffects[function] = {false, false, false};
|
||||
m_functionCalls[function] = {};
|
||||
}
|
||||
|
||||
// Process functions while we have progress. For now, we are only interested
|
||||
@@ -182,8 +185,8 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
|
||||
while (progress)
|
||||
{
|
||||
progress = false;
|
||||
for (auto const& functionName: m_pendingNodes | ranges::views::keys)
|
||||
if (processFunction(functionName))
|
||||
for (FunctionDefinition const* function: m_pendingNodes | ranges::views::keys)
|
||||
if (processFunction(*function))
|
||||
progress = true;
|
||||
}
|
||||
|
||||
@@ -192,57 +195,64 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
|
||||
// If we have not set `canContinue` by now, the function's exit
|
||||
// is not reachable.
|
||||
|
||||
for (auto&& [functionName, calls]: m_functionCalls)
|
||||
// Now it is sufficient to handle the reachable function calls (`m_functionCalls`),
|
||||
// we do not have to consider the control-flow graph anymore.
|
||||
for (auto&& [function, calls]: m_functionCalls)
|
||||
{
|
||||
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
|
||||
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
|
||||
if (sideEffects.canTerminate && sideEffects.canRevert)
|
||||
yulAssert(function);
|
||||
ControlFlowSideEffects& functionSideEffects = m_functionSideEffects[function];
|
||||
auto _visit = [&, visited = std::set<FunctionDefinition const*>{}](FunctionDefinition const& _function, auto&& _recurse) mutable {
|
||||
// Worst side-effects already, stop searching.
|
||||
if (functionSideEffects.canTerminate && functionSideEffects.canRevert)
|
||||
return;
|
||||
if (!visited.insert(_function).second)
|
||||
if (!visited.insert(&_function).second)
|
||||
return;
|
||||
|
||||
ControlFlowSideEffects const* calledSideEffects = nullptr;
|
||||
if (BuiltinFunction const* f = _dialect.builtin(_function))
|
||||
calledSideEffects = &f->controlFlowSideEffects;
|
||||
else
|
||||
calledSideEffects = &m_functionSideEffects.at(_function);
|
||||
for (FunctionCall const* call: m_functionCalls.at(&_function))
|
||||
{
|
||||
ControlFlowSideEffects const& calledSideEffects = sideEffects(*call);
|
||||
if (calledSideEffects.canTerminate)
|
||||
functionSideEffects.canTerminate = true;
|
||||
if (calledSideEffects.canRevert)
|
||||
functionSideEffects.canRevert = true;
|
||||
|
||||
if (calledSideEffects->canTerminate)
|
||||
sideEffects.canTerminate = true;
|
||||
if (calledSideEffects->canRevert)
|
||||
sideEffects.canRevert = true;
|
||||
|
||||
set<YulString> emptySet;
|
||||
for (YulString callee: util::valueOrDefault(m_functionCalls, _function, emptySet))
|
||||
_recurse(callee, _recurse);
|
||||
if (m_functionReferences.count(call))
|
||||
_recurse(*m_functionReferences.at(call), _recurse);
|
||||
}
|
||||
};
|
||||
for (auto const& call: calls)
|
||||
_visit(call, _visit);
|
||||
_visit(*function, _visit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
|
||||
map<YulString, ControlFlowSideEffects> ControlFlowSideEffectsCollector::functionSideEffectsNamed() const
|
||||
{
|
||||
map<YulString, ControlFlowSideEffects> result;
|
||||
for (auto&& [function, sideEffects]: m_functionSideEffects)
|
||||
yulAssert(result.insert({function->name, sideEffects}).second);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ControlFlowSideEffectsCollector::processFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
bool progress = false;
|
||||
while (ControlFlowNode const* node = nextProcessableNode(_name))
|
||||
while (ControlFlowNode const* node = nextProcessableNode(_function))
|
||||
{
|
||||
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
|
||||
if (node == m_cfgBuilder.functionFlows().at(&_function).exit)
|
||||
{
|
||||
m_functionSideEffects[_name].canContinue = true;
|
||||
m_functionSideEffects[&_function].canContinue = true;
|
||||
return true;
|
||||
}
|
||||
for (ControlFlowNode const* s: node->successors)
|
||||
recordReachabilityAndQueue(_name, s);
|
||||
recordReachabilityAndQueue(_function, s);
|
||||
|
||||
progress = true;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
|
||||
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(FunctionDefinition const& _function)
|
||||
{
|
||||
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
|
||||
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[&_function];
|
||||
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
|
||||
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
|
||||
});
|
||||
@@ -254,22 +264,22 @@ ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulS
|
||||
return node;
|
||||
}
|
||||
|
||||
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
|
||||
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(FunctionCall const& _call) const
|
||||
{
|
||||
if (auto const* builtin = m_dialect.builtin(_functionName))
|
||||
if (auto const* builtin = m_dialect.builtin(_call.functionName.name))
|
||||
return builtin->controlFlowSideEffects;
|
||||
else
|
||||
return m_functionSideEffects.at(_functionName);
|
||||
return m_functionSideEffects.at(m_functionReferences.at(&_call));
|
||||
}
|
||||
|
||||
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
|
||||
YulString _functionName,
|
||||
FunctionDefinition const& _function,
|
||||
ControlFlowNode const* _node
|
||||
)
|
||||
{
|
||||
if (_node->functionCall)
|
||||
m_functionCalls[_functionName].insert(*_node->functionCall);
|
||||
if (m_processedNodes[_functionName].insert(_node).second)
|
||||
m_pendingNodes.at(_functionName).push_front(_node);
|
||||
m_functionCalls[&_function].insert(_node->functionCall);
|
||||
if (m_processedNodes[&_function].insert(_node).second)
|
||||
m_pendingNodes.at(&_function).push_front(_node);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ struct Dialect;
|
||||
struct ControlFlowNode
|
||||
{
|
||||
std::vector<ControlFlowNode const*> successors;
|
||||
/// Name of the called function if the node calls a function.
|
||||
std::optional<YulString> functionCall;
|
||||
/// Function call AST node, if present.
|
||||
FunctionCall const* functionCall = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
/// Computes the control-flows of all function defined in the block.
|
||||
/// Assumes the functions are hoisted to the topmost block.
|
||||
explicit ControlFlowBuilder(Block const& _ast);
|
||||
std::map<YulString, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
|
||||
std::map<FunctionDefinition const*, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
|
||||
|
||||
private:
|
||||
using ASTWalker::operator();
|
||||
@@ -79,12 +79,14 @@ private:
|
||||
ControlFlowNode const* m_break = nullptr;
|
||||
ControlFlowNode const* m_continue = nullptr;
|
||||
|
||||
std::map<YulString, FunctionFlow> m_functionFlows;
|
||||
std::map<FunctionDefinition const*, FunctionFlow> m_functionFlows;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Requires: Disambiguator, Function Hoister.
|
||||
* Computes control-flow side-effects for user-defined functions.
|
||||
* Source does not have to be disambiguated, unless you want the side-effects
|
||||
* based on function names.
|
||||
*/
|
||||
class ControlFlowSideEffectsCollector
|
||||
{
|
||||
@@ -94,36 +96,43 @@ public:
|
||||
Block const& _ast
|
||||
);
|
||||
|
||||
std::map<YulString, ControlFlowSideEffects> const& functionSideEffects() const
|
||||
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& functionSideEffects() const
|
||||
{
|
||||
return m_functionSideEffects;
|
||||
}
|
||||
/// Returns the side effects by function name, requires unique function names.
|
||||
std::map<YulString, ControlFlowSideEffects> functionSideEffectsNamed() const;
|
||||
private:
|
||||
|
||||
/// @returns false if nothing could be processed.
|
||||
bool processFunction(YulString _name);
|
||||
bool processFunction(FunctionDefinition const& _function);
|
||||
|
||||
/// @returns the next pending node of the function that is not
|
||||
/// a function call to a function that might not continue.
|
||||
/// De-queues the node or returns nullptr if no such node is found.
|
||||
ControlFlowNode const* nextProcessableNode(YulString _functionName);
|
||||
ControlFlowNode const* nextProcessableNode(FunctionDefinition const& _function);
|
||||
|
||||
/// @returns the side-effects of either a builtin call or a user defined function
|
||||
/// call (as far as already computed).
|
||||
ControlFlowSideEffects const& sideEffects(YulString _functionName) const;
|
||||
ControlFlowSideEffects const& sideEffects(FunctionCall const& _call) const;
|
||||
|
||||
/// Queues the given node to be processed (if not already visited)
|
||||
/// and if it is a function call, records that `_functionName` calls
|
||||
/// `*_node->functionCall`.
|
||||
void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node);
|
||||
void recordReachabilityAndQueue(FunctionDefinition const& _function, ControlFlowNode const* _node);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
ControlFlowBuilder m_cfgBuilder;
|
||||
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
|
||||
std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes;
|
||||
std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes;
|
||||
/// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y`
|
||||
std::map<YulString, std::set<YulString>> m_functionCalls;
|
||||
/// Function references, but only for calls to user-defined functions.
|
||||
std::map<FunctionCall const*, FunctionDefinition const*> m_functionReferences;
|
||||
/// Side effects of user-defined functions, is being constructod.
|
||||
std::map<FunctionDefinition const*, ControlFlowSideEffects> m_functionSideEffects;
|
||||
/// Control flow nodes still to process, per function.
|
||||
std::map<FunctionDefinition const*, std::list<ControlFlowNode const*>> m_pendingNodes;
|
||||
/// Control flow nodes already processed, per function.
|
||||
std::map<FunctionDefinition const*, std::set<ControlFlowNode const*>> m_processedNodes;
|
||||
/// Set of reachable function calls nodes in each function (including calls to builtins).
|
||||
std::map<FunctionDefinition const*, std::set<FunctionCall const*>> m_functionCalls;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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/FunctionReferenceResolver.h>
|
||||
|
||||
#include <libyul/AST.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::yul;
|
||||
using namespace solidity::util;
|
||||
|
||||
FunctionReferenceResolver::FunctionReferenceResolver(Block const& _ast)
|
||||
{
|
||||
(*this)(_ast);
|
||||
yulAssert(m_scopes.empty());
|
||||
}
|
||||
|
||||
void FunctionReferenceResolver::operator()(FunctionCall const& _functionCall)
|
||||
{
|
||||
for (auto&& scope: m_scopes | ranges::views::reverse)
|
||||
if (FunctionDefinition const** function = util::valueOrNullptr(scope, _functionCall.functionName.name))
|
||||
{
|
||||
m_functionReferences[&_functionCall] = *function;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we did not find anything, it was a builtin call.
|
||||
|
||||
ASTWalker::operator()(_functionCall);
|
||||
}
|
||||
|
||||
void FunctionReferenceResolver::operator()(Block const& _block)
|
||||
{
|
||||
m_scopes.emplace_back();
|
||||
for (auto const& statement: _block.statements)
|
||||
if (auto const* function = get_if<FunctionDefinition>(&statement))
|
||||
m_scopes.back()[function->name] = function;
|
||||
|
||||
ASTWalker::operator()(_block);
|
||||
|
||||
m_scopes.pop_back();
|
||||
}
|
||||
+15
-11
@@ -14,31 +14,35 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* AST walker that finds all function definitions and stores them into a map indexed by the function names.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* AST walker that finds all function definitions and stores them into a map indexed by the function names.
|
||||
* Resolves references to user-defined functions in function calls.
|
||||
* Assumes the code is correct, i.e. does not check for references to be valid or unique.
|
||||
*
|
||||
* Prerequisite: Disambiguator
|
||||
* Be careful not to iterate over the result - it is not deterministic.
|
||||
*/
|
||||
class FunctionDefinitionCollector: ASTWalker
|
||||
class FunctionReferenceResolver: private ASTWalker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, FunctionDefinition const*> run(Block const& _block);
|
||||
explicit FunctionReferenceResolver(Block const& _ast);
|
||||
std::map<FunctionCall const*, FunctionDefinition const*> const& references() const { return m_functionReferences; }
|
||||
|
||||
private:
|
||||
using ASTWalker::operator();
|
||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||
std::map<YulString, FunctionDefinition const*> m_functionDefinitions;
|
||||
void operator()(FunctionCall const& _functionCall) override;
|
||||
void operator()(Block const& _block) override;
|
||||
|
||||
std::map<FunctionCall const*, FunctionDefinition const*> m_functionReferences;
|
||||
std::vector<std::map<YulString, FunctionDefinition const*>> m_scopes;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -176,9 +176,9 @@ StackSlot ControlFlowGraphBuilder::operator()(Expression const& _expression)
|
||||
|
||||
StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call)
|
||||
{
|
||||
CFG::Operation const& operation = visitFunctionCall(_call);
|
||||
yulAssert(operation.output.size() == 1, "");
|
||||
return operation.output.front();
|
||||
Stack const& output = visitFunctionCall(_call);
|
||||
yulAssert(output.size() == 1, "");
|
||||
return output.front();
|
||||
}
|
||||
|
||||
void ControlFlowGraphBuilder::operator()(VariableDeclaration const& _varDecl)
|
||||
@@ -219,8 +219,8 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
||||
yulAssert(m_currentBlock, "");
|
||||
std::visit(util::GenericVisitor{
|
||||
[&](FunctionCall const& _call) {
|
||||
CFG::Operation const& operation = visitFunctionCall(_call);
|
||||
yulAssert(operation.output.empty(), "");
|
||||
Stack const& output = visitFunctionCall(_call);
|
||||
yulAssert(output.empty(), "");
|
||||
},
|
||||
[&](auto const&) { yulAssert(false, ""); }
|
||||
}, _exprStmt.expression);
|
||||
@@ -239,6 +239,9 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
||||
void ControlFlowGraphBuilder::operator()(Block const& _block)
|
||||
{
|
||||
ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get());
|
||||
for (auto const& statement: _block.statements)
|
||||
if (auto const* function = get_if<FunctionDefinition>(&statement))
|
||||
registerFunction(*function);
|
||||
for (auto const& statement: _block.statements)
|
||||
std::visit(*this, statement);
|
||||
}
|
||||
@@ -386,11 +389,26 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
|
||||
Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
|
||||
m_graph.functions.emplace_back(&function);
|
||||
|
||||
CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function);
|
||||
|
||||
ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect};
|
||||
builder.m_currentFunction = &functionInfo;
|
||||
builder.m_currentBlock = functionInfo.entry;
|
||||
builder(_function.body);
|
||||
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
|
||||
}
|
||||
|
||||
void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
yulAssert(m_scope, "");
|
||||
yulAssert(m_scope->identifiers.count(_function.name), "");
|
||||
Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
|
||||
|
||||
yulAssert(m_info.scopes.at(&_function.body), "");
|
||||
Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
|
||||
yulAssert(virtualFunctionScope, "");
|
||||
|
||||
auto&& [it, inserted] = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
|
||||
bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
|
||||
_function.debugData,
|
||||
function,
|
||||
&m_graph.makeBlock(debugDataOf(_function.body)),
|
||||
@@ -406,19 +424,11 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
|
||||
_retVar.debugData
|
||||
};
|
||||
}) | ranges::to<vector>
|
||||
}));
|
||||
yulAssert(inserted, "");
|
||||
CFG::FunctionInfo& functionInfo = it->second;
|
||||
|
||||
ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect};
|
||||
builder.m_currentFunction = &functionInfo;
|
||||
builder.m_currentBlock = functionInfo.entry;
|
||||
builder(_function.body);
|
||||
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
|
||||
})).second;
|
||||
yulAssert(inserted);
|
||||
}
|
||||
|
||||
|
||||
CFG::Operation const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call)
|
||||
Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call)
|
||||
{
|
||||
yulAssert(m_scope, "");
|
||||
yulAssert(m_currentBlock, "");
|
||||
@@ -439,7 +449,7 @@ CFG::Operation const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall co
|
||||
}) | ranges::to<Stack>,
|
||||
// operation
|
||||
move(builtinCall)
|
||||
});
|
||||
}).output;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -456,7 +466,7 @@ CFG::Operation const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall co
|
||||
}) | ranges::to<Stack>,
|
||||
// operation
|
||||
CFG::FunctionCall{_call.debugData, function, _call}
|
||||
});
|
||||
}).output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,9 +474,9 @@ Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _e
|
||||
{
|
||||
return std::visit(util::GenericVisitor{
|
||||
[&](FunctionCall const& _call) -> Stack {
|
||||
CFG::Operation const& operation = visitFunctionCall(_call);
|
||||
yulAssert(_expectedSlotCount == operation.output.size(), "");
|
||||
return operation.output;
|
||||
Stack const& output = visitFunctionCall(_call);
|
||||
yulAssert(_expectedSlotCount == output.size(), "");
|
||||
return output;
|
||||
},
|
||||
[&](auto const& _identifierOrLiteral) -> Stack {
|
||||
yulAssert(_expectedSlotCount == 1, "");
|
||||
|
||||
@@ -57,7 +57,8 @@ private:
|
||||
AsmAnalysisInfo const& _analysisInfo,
|
||||
Dialect const& _dialect
|
||||
);
|
||||
CFG::Operation const& visitFunctionCall(FunctionCall const&);
|
||||
void registerFunction(FunctionDefinition const& _function);
|
||||
Stack const& visitFunctionCall(FunctionCall const&);
|
||||
Stack visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount);
|
||||
|
||||
Scope::Function const& lookupFunction(YulString _name) const;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <libyul/backends/evm/EVMCodeTransform.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/backends/evm/OptimizedEVMCodeTransform.h>
|
||||
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
@@ -62,19 +63,35 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
|
||||
|
||||
yulAssert(_object.analysisInfo, "No analysis info.");
|
||||
yulAssert(_object.code, "No code.");
|
||||
// We do not catch and re-throw the stack too deep exception here because it is a YulException,
|
||||
// which should be native to this part of the code.
|
||||
CodeTransform transform{
|
||||
m_assembly,
|
||||
*_object.analysisInfo,
|
||||
*_object.code,
|
||||
m_dialect,
|
||||
context,
|
||||
_optimize,
|
||||
{},
|
||||
CodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
|
||||
};
|
||||
transform(*_object.code);
|
||||
if (!transform.stackErrors().empty())
|
||||
BOOST_THROW_EXCEPTION(transform.stackErrors().front());
|
||||
if (_optimize && m_dialect.evmVersion().canOverchargeGasForCall())
|
||||
{
|
||||
auto stackErrors = OptimizedEVMCodeTransform::run(
|
||||
m_assembly,
|
||||
*_object.analysisInfo,
|
||||
*_object.code,
|
||||
m_dialect,
|
||||
context,
|
||||
OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
|
||||
);
|
||||
if (!stackErrors.empty())
|
||||
BOOST_THROW_EXCEPTION(stackErrors.front());
|
||||
}
|
||||
else
|
||||
{
|
||||
// We do not catch and re-throw the stack too deep exception here because it is a YulException,
|
||||
// which should be native to this part of the code.
|
||||
CodeTransform transform{
|
||||
m_assembly,
|
||||
*_object.analysisInfo,
|
||||
*_object.code,
|
||||
m_dialect,
|
||||
context,
|
||||
_optimize,
|
||||
{},
|
||||
CodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
|
||||
};
|
||||
transform(*_object.code);
|
||||
if (!transform.stackErrors().empty())
|
||||
BOOST_THROW_EXCEPTION(transform.stackErrors().front());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ vector<StackTooDeepError> OptimizedEVMCodeTransform::run(
|
||||
Block const& _block,
|
||||
EVMDialect const& _dialect,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _useNamedLabelsForFunctions
|
||||
UseNamedLabels _useNamedLabelsForFunctions
|
||||
)
|
||||
{
|
||||
std::unique_ptr<CFG> dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _block);
|
||||
@@ -170,15 +170,35 @@ void OptimizedEVMCodeTransform::operator()(CFG::Assignment const& _assignment)
|
||||
OptimizedEVMCodeTransform::OptimizedEVMCodeTransform(
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
UseNamedLabels _useNamedLabelsForFunctions,
|
||||
CFG const& _dfg,
|
||||
StackLayout const& _stackLayout
|
||||
):
|
||||
m_assembly(_assembly),
|
||||
m_builtinContext(_builtinContext),
|
||||
m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
|
||||
m_dfg(_dfg),
|
||||
m_stackLayout(_stackLayout)
|
||||
m_stackLayout(_stackLayout),
|
||||
m_functionLabels([&](){
|
||||
map<CFG::FunctionInfo const*, AbstractAssembly::LabelID> functionLabels;
|
||||
set<YulString> assignedFunctionNames;
|
||||
for (Scope::Function const* function: m_dfg.functions)
|
||||
{
|
||||
CFG::FunctionInfo const& functionInfo = m_dfg.functionInfo.at(function);
|
||||
bool nameAlreadySeen = !assignedFunctionNames.insert(function->name).second;
|
||||
if (_useNamedLabelsForFunctions == UseNamedLabels::YesAndForceUnique)
|
||||
yulAssert(!nameAlreadySeen);
|
||||
bool useNamedLabel = _useNamedLabelsForFunctions != UseNamedLabels::Never && !nameAlreadySeen;
|
||||
functionLabels[&functionInfo] = useNamedLabel ?
|
||||
m_assembly.namedLabel(
|
||||
function->name.str(),
|
||||
function->arguments.size(),
|
||||
function->returns.size(),
|
||||
functionInfo.debugData ? functionInfo.debugData->astID : nullopt
|
||||
) :
|
||||
m_assembly.newLabelId();
|
||||
}
|
||||
return functionLabels;
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -191,15 +211,7 @@ void OptimizedEVMCodeTransform::assertLayoutCompatibility(Stack const& _currentS
|
||||
|
||||
AbstractAssembly::LabelID OptimizedEVMCodeTransform::getFunctionLabel(Scope::Function const& _function)
|
||||
{
|
||||
CFG::FunctionInfo const& functionInfo = m_dfg.functionInfo.at(&_function);
|
||||
if (!m_functionLabels.count(&functionInfo))
|
||||
m_functionLabels[&functionInfo] = m_useNamedLabelsForFunctions ? m_assembly.namedLabel(
|
||||
functionInfo.function.name.str(),
|
||||
functionInfo.function.arguments.size(),
|
||||
functionInfo.function.returns.size(),
|
||||
{}
|
||||
) : m_assembly.newLabelId();
|
||||
return m_functionLabels[&functionInfo];
|
||||
return m_functionLabels.at(&m_dfg.functionInfo.at(&_function));
|
||||
}
|
||||
|
||||
void OptimizedEVMCodeTransform::validateSlot(StackSlot const& _slot, Expression const& _expression)
|
||||
|
||||
@@ -43,13 +43,17 @@ struct StackLayout;
|
||||
class OptimizedEVMCodeTransform
|
||||
{
|
||||
public:
|
||||
/// Use named labels for functions 1) Yes and check that the names are unique
|
||||
/// 2) For none of the functions 3) for the first function of each name.
|
||||
enum class UseNamedLabels { YesAndForceUnique, Never, ForFirstFunctionOfEachName };
|
||||
|
||||
[[nodiscard]] static std::vector<StackTooDeepError> run(
|
||||
AbstractAssembly& _assembly,
|
||||
AsmAnalysisInfo& _analysisInfo,
|
||||
Block const& _block,
|
||||
EVMDialect const& _dialect,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _useNamedLabelsForFunctions = false
|
||||
UseNamedLabels _useNamedLabelsForFunctions
|
||||
);
|
||||
|
||||
/// Generate code for the function call @a _call. Only public for using with std::visit.
|
||||
@@ -62,7 +66,7 @@ private:
|
||||
OptimizedEVMCodeTransform(
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
UseNamedLabels _useNamedLabelsForFunctions,
|
||||
CFG const& _dfg,
|
||||
StackLayout const& _stackLayout
|
||||
);
|
||||
@@ -70,6 +74,7 @@ private:
|
||||
/// Assert that it is valid to transition from @a _currentStack to @a _desiredStack.
|
||||
/// That is @a _currentStack matches each slot in @a _desiredStack that is not a JunkSlot exactly.
|
||||
static void assertLayoutCompatibility(Stack const& _currentStack, Stack const& _desiredStack);
|
||||
|
||||
/// @returns The label of the entry point of the given @a _function.
|
||||
/// Creates and stores a new label, if none exists already.
|
||||
AbstractAssembly::LabelID getFunctionLabel(Scope::Function const& _function);
|
||||
@@ -94,13 +99,12 @@ private:
|
||||
|
||||
AbstractAssembly& m_assembly;
|
||||
BuiltinContext& m_builtinContext;
|
||||
bool m_useNamedLabelsForFunctions = true;
|
||||
CFG const& m_dfg;
|
||||
StackLayout const& m_stackLayout;
|
||||
Stack m_stack;
|
||||
std::map<yul::FunctionCall const*, AbstractAssembly::LabelID> m_returnLabels;
|
||||
std::map<CFG::BasicBlock const*, AbstractAssembly::LabelID> m_blockLabels;
|
||||
std::map<CFG::FunctionInfo const*, AbstractAssembly::LabelID> m_functionLabels;
|
||||
std::map<CFG::FunctionInfo const*, AbstractAssembly::LabelID> const m_functionLabels;
|
||||
/// Set of blocks already generated. If any of the contained blocks is ever jumped to, m_blockLabels should
|
||||
/// contain a jump label for it.
|
||||
std::set<CFG::BasicBlock const*> m_generated;
|
||||
|
||||
@@ -102,4 +102,34 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <
|
||||
typename Node,
|
||||
typename Visitor,
|
||||
typename Base = std::conditional_t<std::is_const_v<Node>, ASTWalker, ASTModifier>
|
||||
>
|
||||
struct ForEach: Base
|
||||
{
|
||||
ForEach(Visitor& _visitor): visitor(_visitor) {}
|
||||
|
||||
using Base::operator();
|
||||
void operator()(Node& _node) override
|
||||
{
|
||||
visitor(_node);
|
||||
Base::operator()(_node);
|
||||
}
|
||||
|
||||
Visitor& visitor;
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper function that traverses the AST and calls the visitor for each
|
||||
/// node of a specific type.
|
||||
template<typename Node, typename Entry, typename Visitor>
|
||||
void forEach(Entry&& _entry, Visitor&& _visitor)
|
||||
{
|
||||
detail::ForEach<Node, Visitor&>{_visitor}(_entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
using namespace std;
|
||||
@@ -26,6 +27,14 @@ using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
using namespace solidity::util;
|
||||
|
||||
void ConditionalSimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
ConditionalSimplifier{
|
||||
_context.dialect,
|
||||
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed()
|
||||
}(_ast);
|
||||
}
|
||||
|
||||
void ConditionalSimplifier::operator()(Switch& _switch)
|
||||
{
|
||||
visit(*_switch.expression);
|
||||
@@ -65,7 +74,7 @@ void ConditionalSimplifier::operator()(Block& _block)
|
||||
if (
|
||||
holds_alternative<Identifier>(*_if.condition) &&
|
||||
!_if.body.statements.empty() &&
|
||||
TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) !=
|
||||
TerminationFinder(m_dialect, &m_functionSideEffects).controlFlowKind(_if.body.statements.back()) !=
|
||||
TerminationFinder::ControlFlow::FlowOut
|
||||
)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace solidity::yul
|
||||
*
|
||||
* Future features:
|
||||
* - allow replacements by "1"
|
||||
* - take termination of user-defined functions into account
|
||||
*
|
||||
* Works best with SSA form and if dead code removal has run before.
|
||||
*
|
||||
@@ -54,20 +53,21 @@ class ConditionalSimplifier: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"ConditionalSimplifier"};
|
||||
static void run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
ConditionalSimplifier{_context.dialect}(_ast);
|
||||
}
|
||||
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(Switch& _switch) override;
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
private:
|
||||
explicit ConditionalSimplifier(Dialect const& _dialect):
|
||||
m_dialect(_dialect)
|
||||
explicit ConditionalSimplifier(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, ControlFlowSideEffects> _sideEffects
|
||||
):
|
||||
m_dialect(_dialect), m_functionSideEffects(move(_sideEffects))
|
||||
{}
|
||||
Dialect const& m_dialect;
|
||||
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
using namespace std;
|
||||
@@ -27,6 +28,14 @@ using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
using namespace solidity::util;
|
||||
|
||||
void ConditionalUnsimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
ConditionalUnsimplifier{
|
||||
_context.dialect,
|
||||
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed()
|
||||
}(_ast);
|
||||
}
|
||||
|
||||
void ConditionalUnsimplifier::operator()(Switch& _switch)
|
||||
{
|
||||
visit(*_switch.expression);
|
||||
@@ -78,7 +87,7 @@ void ConditionalUnsimplifier::operator()(Block& _block)
|
||||
YulString condition = std::get<Identifier>(*_if.condition).name;
|
||||
if (
|
||||
holds_alternative<Assignment>(_stmt2) &&
|
||||
TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) !=
|
||||
TerminationFinder(m_dialect, &m_functionSideEffects).controlFlowKind(_if.body.statements.back()) !=
|
||||
TerminationFinder::ControlFlow::FlowOut
|
||||
)
|
||||
{
|
||||
|
||||
@@ -33,20 +33,21 @@ class ConditionalUnsimplifier: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"ConditionalUnsimplifier"};
|
||||
static void run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
ConditionalUnsimplifier{_context.dialect}(_ast);
|
||||
}
|
||||
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(Switch& _switch) override;
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
private:
|
||||
explicit ConditionalUnsimplifier(Dialect const& _dialect):
|
||||
m_dialect(_dialect)
|
||||
explicit ConditionalUnsimplifier(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, ControlFlowSideEffects> const& _sideEffects
|
||||
):
|
||||
m_dialect(_dialect), m_functionSideEffects(_sideEffects)
|
||||
{}
|
||||
Dialect const& m_dialect;
|
||||
std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ DataFlowAnalyzer::DataFlowAnalyzer(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, SideEffects> _functionSideEffects
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_functionSideEffects(std::move(_functionSideEffects)),
|
||||
m_knowledgeBase(_dialect, m_value)
|
||||
m_dialect(_dialect),
|
||||
m_functionSideEffects(std::move(_functionSideEffects)),
|
||||
m_knowledgeBase(_dialect, m_value)
|
||||
{
|
||||
if (auto const* builtin = _dialect.memoryStoreFunction(YulString{}))
|
||||
m_storeFunctionName[static_cast<unsigned>(StoreLoadLocation::Memory)] = builtin->name;
|
||||
@@ -123,9 +123,7 @@ void DataFlowAnalyzer::operator()(If& _if)
|
||||
|
||||
joinKnowledge(storage, memory);
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_if.body);
|
||||
clearValues(assignments.names());
|
||||
clearValues(assignedVariableNames(_if.body));
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Switch& _switch)
|
||||
@@ -140,11 +138,10 @@ void DataFlowAnalyzer::operator()(Switch& _switch)
|
||||
(*this)(_case.body);
|
||||
joinKnowledge(storage, memory);
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_case.body);
|
||||
assignedVariables += assignments.names();
|
||||
set<YulString> variables = assignedVariableNames(_case.body);
|
||||
assignedVariables += variables;
|
||||
// This is a little too destructive, we could retain the old values.
|
||||
clearValues(assignments.names());
|
||||
clearValues(variables);
|
||||
clearKnowledgeIfInvalidated(_case.body);
|
||||
}
|
||||
for (auto& _case: _switch.cases)
|
||||
@@ -190,10 +187,9 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
AssignmentsSinceContinue assignmentsSinceCont;
|
||||
assignmentsSinceCont(_for.body);
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
assignments(_for.post);
|
||||
clearValues(assignments.names());
|
||||
set<YulString> assignedVariables =
|
||||
assignedVariableNames(_for.body) + assignedVariableNames(_for.post);
|
||||
clearValues(assignedVariables);
|
||||
|
||||
// break/continue are tricky for storage and thus we almost always clear here.
|
||||
clearKnowledgeIfInvalidated(*_for.condition);
|
||||
@@ -205,7 +201,7 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
clearValues(assignmentsSinceCont.names());
|
||||
clearKnowledgeIfInvalidated(_for.body);
|
||||
(*this)(_for.post);
|
||||
clearValues(assignments.names());
|
||||
clearValues(assignedVariables);
|
||||
clearKnowledgeIfInvalidated(*_for.condition);
|
||||
clearKnowledgeIfInvalidated(_for.post);
|
||||
clearKnowledgeIfInvalidated(_for.body);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <libyul/optimiser/DeadCodeEliminator.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
#include <libyul/AST.h>
|
||||
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
@@ -36,7 +37,11 @@ using namespace solidity::yul;
|
||||
|
||||
void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
DeadCodeEliminator{_context.dialect}(_ast);
|
||||
ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast);
|
||||
DeadCodeEliminator{
|
||||
_context.dialect,
|
||||
sideEffects.functionSideEffectsNamed()
|
||||
}(_ast);
|
||||
}
|
||||
|
||||
void DeadCodeEliminator::operator()(ForLoop& _for)
|
||||
@@ -49,7 +54,7 @@ void DeadCodeEliminator::operator()(Block& _block)
|
||||
{
|
||||
TerminationFinder::ControlFlow controlFlowChange;
|
||||
size_t index;
|
||||
tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements);
|
||||
tie(controlFlowChange, index) = TerminationFinder{m_dialect, &m_functionSideEffects}.firstUnconditionalControlFlowChange(_block.statements);
|
||||
|
||||
// Erase everything after the terminating statement that is not a function definition.
|
||||
if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != std::numeric_limits<size_t>::max())
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/ControlFlowSideEffects.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -36,7 +37,9 @@ struct OptimiserStepContext;
|
||||
* Optimisation stage that removes unreachable code
|
||||
*
|
||||
* Unreachable code is any code within a block which is preceded by a
|
||||
* leave, return, invalid, break, continue, selfdestruct or revert.
|
||||
* leave, return, invalid, break, continue, selfdestruct or revert or
|
||||
* a call to a user-defined function that never returns (either due to
|
||||
* recursion or a call to return / revert / stop).
|
||||
*
|
||||
* Function definitions are retained as they might be called by earlier
|
||||
* code and thus are considered reachable.
|
||||
@@ -57,9 +60,13 @@ public:
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
private:
|
||||
DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
DeadCodeEliminator(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, ControlFlowSideEffects> _sideEffects
|
||||
): m_dialect(_dialect), m_functionSideEffects(move(_sideEffects)) {}
|
||||
|
||||
Dialect const& m_dialect;
|
||||
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||
#include <libyul/AST.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
|
||||
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block)
|
||||
{
|
||||
FunctionDefinitionCollector functionDefinitionCollector;
|
||||
functionDefinitionCollector(_block);
|
||||
return functionDefinitionCollector.m_functionDefinitions;
|
||||
}
|
||||
|
||||
void FunctionDefinitionCollector::operator()(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
m_functionDefinitions[_functionDefinition.name] = &_functionDefinition;
|
||||
ASTWalker::operator()(_functionDefinition);
|
||||
}
|
||||
@@ -78,13 +78,6 @@ map<YulString, size_t> ReferencesCounter::countReferences(Expression const& _exp
|
||||
return counter.references();
|
||||
}
|
||||
|
||||
void Assignments::operator()(Assignment const& _assignment)
|
||||
{
|
||||
for (auto const& var: _assignment.variableNames)
|
||||
m_names.emplace(var.name);
|
||||
}
|
||||
|
||||
|
||||
void AssignmentsSinceContinue::operator()(ForLoop const& _forLoop)
|
||||
{
|
||||
m_forLoopDepth++;
|
||||
@@ -109,3 +102,22 @@ void AssignmentsSinceContinue::operator()(FunctionDefinition const&)
|
||||
{
|
||||
yulAssert(false, "");
|
||||
}
|
||||
|
||||
std::set<YulString> solidity::yul::assignedVariableNames(Block const& _code)
|
||||
{
|
||||
std::set<YulString> names;
|
||||
forEach<Assignment const>(_code, [&](Assignment const& _assignment) {
|
||||
for (auto const& var: _assignment.variableNames)
|
||||
names.emplace(var.name);
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
map<YulString, FunctionDefinition const*> solidity::yul::allFunctionDefinitions(Block const& _block)
|
||||
{
|
||||
std::map<YulString, FunctionDefinition const*> result;
|
||||
forEach<FunctionDefinition const>(_block, [&](FunctionDefinition const& _function) {
|
||||
result[_function.name] = &_function;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -91,20 +91,6 @@ private:
|
||||
std::map<YulString, size_t> m_references;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specific AST walker that finds all variables that are assigned to.
|
||||
*/
|
||||
class Assignments: public ASTWalker
|
||||
{
|
||||
public:
|
||||
using ASTWalker::operator ();
|
||||
void operator()(Assignment const& _assignment) override;
|
||||
|
||||
std::set<YulString> const& names() const { return m_names; }
|
||||
private:
|
||||
std::set<YulString> m_names;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects all names from a given continue statement on onwards.
|
||||
*
|
||||
@@ -130,4 +116,12 @@ private:
|
||||
std::set<YulString> m_names;
|
||||
};
|
||||
|
||||
/// @returns the names of all variables that are assigned to inside @a _code.
|
||||
/// (ignores variable declarations)
|
||||
std::set<YulString> assignedVariableNames(Block const& _code);
|
||||
|
||||
/// @returns all function definitions anywhere in the AST.
|
||||
/// Requires disambiguated source.
|
||||
std::map<YulString, FunctionDefinition const*> allFunctionDefinitions(Block const& _block);
|
||||
|
||||
}
|
||||
|
||||
@@ -196,12 +196,7 @@ void IntroduceControlFlowSSA::operator()(ForLoop& _for)
|
||||
{
|
||||
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
assignments(_for.post);
|
||||
|
||||
|
||||
for (auto const& var: assignments.names())
|
||||
for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
|
||||
if (m_variablesInScope.count(var))
|
||||
m_variablesToReassign.insert(var);
|
||||
|
||||
@@ -359,11 +354,7 @@ void PropagateValues::operator()(ForLoop& _for)
|
||||
{
|
||||
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
assignments(_for.post);
|
||||
|
||||
for (auto const& var: assignments.names())
|
||||
for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
|
||||
m_currentVariableValues.erase(var);
|
||||
|
||||
visit(*_for.condition);
|
||||
@@ -389,11 +380,10 @@ void PropagateValues::operator()(Block& _block)
|
||||
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
TypeInfo typeInfo(_context.dialect, _ast);
|
||||
Assignments assignments;
|
||||
assignments(_ast);
|
||||
IntroduceSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
|
||||
IntroduceControlFlowSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
|
||||
PropagateValues{assignments.names()}(_ast);
|
||||
set<YulString> assignedVariables = assignedVariableNames(_ast);
|
||||
IntroduceSSA{_context.dispenser, assignedVariables, typeInfo}(_ast);
|
||||
IntroduceControlFlowSSA{_context.dispenser, assignedVariables, typeInfo}(_ast);
|
||||
PropagateValues{assignedVariables}(_ast);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -182,8 +182,19 @@ pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUncondition
|
||||
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
|
||||
{
|
||||
if (
|
||||
holds_alternative<VariableDeclaration>(_statement) &&
|
||||
std::get<VariableDeclaration>(_statement).value &&
|
||||
containsNonContinuingFunctionCall(*std::get<VariableDeclaration>(_statement).value)
|
||||
)
|
||||
return ControlFlow::Terminate;
|
||||
else if (
|
||||
holds_alternative<Assignment>(_statement) &&
|
||||
containsNonContinuingFunctionCall(*std::get<Assignment>(_statement).value)
|
||||
)
|
||||
return ControlFlow::Terminate;
|
||||
else if (
|
||||
holds_alternative<ExpressionStatement>(_statement) &&
|
||||
isTerminatingBuiltin(std::get<ExpressionStatement>(_statement))
|
||||
containsNonContinuingFunctionCall(std::get<ExpressionStatement>(_statement).expression)
|
||||
)
|
||||
return ControlFlow::Terminate;
|
||||
else if (holds_alternative<Break>(_statement))
|
||||
@@ -196,10 +207,18 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons
|
||||
return ControlFlow::FlowOut;
|
||||
}
|
||||
|
||||
bool TerminationFinder::isTerminatingBuiltin(ExpressionStatement const& _exprStmnt)
|
||||
bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _expr)
|
||||
{
|
||||
if (holds_alternative<FunctionCall>(_exprStmnt.expression))
|
||||
if (auto instruction = toEVMInstruction(m_dialect, std::get<FunctionCall>(_exprStmnt.expression).functionName.name))
|
||||
return evmasm::SemanticInformation::terminatesControlFlow(*instruction);
|
||||
if (auto functionCall = std::get_if<FunctionCall>(&_expr))
|
||||
{
|
||||
for (auto const& arg: functionCall->arguments)
|
||||
if (containsNonContinuingFunctionCall(arg))
|
||||
return true;
|
||||
|
||||
if (auto builtin = m_dialect.builtin(functionCall->functionName.name))
|
||||
return !builtin->controlFlowSideEffects.canContinue;
|
||||
else if (m_functionSideEffects && m_functionSideEffects->count(functionCall->functionName.name))
|
||||
return !m_functionSideEffects->at(functionCall->functionName.name).canContinue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,22 +205,31 @@ private:
|
||||
std::set<YulString> m_variableReferences;
|
||||
};
|
||||
|
||||
struct ControlFlowSideEffects;
|
||||
|
||||
/**
|
||||
* Helper class to find "irregular" control flow.
|
||||
* This includes termination, break and continue.
|
||||
* This includes termination, break, continue and leave.
|
||||
* In general, it is applied only to "simple" statements. The control-flow
|
||||
* of loops, switches and if statements is always "FlowOut" with the assumption
|
||||
* that the caller will descend into them.
|
||||
*/
|
||||
class TerminationFinder
|
||||
{
|
||||
public:
|
||||
// TODO check all uses of TerminationFinder!
|
||||
/// "Terminate" here means that there is no continuing control-flow.
|
||||
/// If this is applied to a function that can revert or stop, but can also
|
||||
/// exit regularly, the property is set to "FlowOut".
|
||||
enum class ControlFlow { FlowOut, Break, Continue, Terminate, Leave };
|
||||
|
||||
TerminationFinder(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
TerminationFinder(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, ControlFlowSideEffects> const* _functionSideEffects = nullptr
|
||||
): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {}
|
||||
|
||||
/// @returns the index of the first statement in the provided sequence
|
||||
/// that is an unconditional ``break``, ``continue``, ``leave`` or a
|
||||
/// call to a terminating builtin function.
|
||||
/// call to a terminating function.
|
||||
/// If control flow can continue at the end of the list,
|
||||
/// returns `FlowOut` and ``size_t(-1)``.
|
||||
/// The function might return ``FlowOut`` even though control
|
||||
@@ -233,13 +242,14 @@ public:
|
||||
/// This function could return FlowOut even if control flow never continues.
|
||||
ControlFlow controlFlowKind(Statement const& _statement);
|
||||
|
||||
/// @returns true if the expression statement is a direct
|
||||
/// call to a builtin terminating function like
|
||||
/// ``stop``, ``revert`` or ``return``.
|
||||
bool isTerminatingBuiltin(ExpressionStatement const& _exprStmnt);
|
||||
/// @returns true if the expression contains a
|
||||
/// call to a terminating function, i.e. a function that does not have
|
||||
/// a regular "flow out" control-flow (it might also be recursive).
|
||||
bool containsNonContinuingFunctionCall(Expression const& _expr);
|
||||
|
||||
private:
|
||||
Dialect const& m_dialect;
|
||||
std::map<YulString, ControlFlowSideEffects> const* m_functionSideEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@
|
||||
#include <libyul/optimiser/Metrics.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
|
||||
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
|
||||
#include <libyul/backends/evm/StackHelpers.h>
|
||||
#include <libyul/backends/evm/StackLayoutGenerator.h>
|
||||
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
|
||||
#include <libyul/CompilabilityChecker.h>
|
||||
|
||||
#include <libyul/AST.h>
|
||||
@@ -162,6 +169,50 @@ void eliminateVariables(
|
||||
UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization);
|
||||
}
|
||||
|
||||
void eliminateVariables(
|
||||
Dialect const& _dialect,
|
||||
Block& _block,
|
||||
vector<StackLayoutGenerator::StackTooDeep> const& _unreachables,
|
||||
bool _allowMSizeOptimization
|
||||
)
|
||||
{
|
||||
RematCandidateSelector selector{_dialect};
|
||||
selector(_block);
|
||||
std::map<YulString, size_t> candidates;
|
||||
for (auto [cost, candidatesWithCost]: selector.candidates())
|
||||
for (auto candidate: candidatesWithCost)
|
||||
candidates[get<0>(candidate)] = cost;
|
||||
|
||||
set<YulString> varsToEliminate;
|
||||
|
||||
// TODO: this currently ignores the fact that variables may reference other variables we want to eliminate.
|
||||
for (auto const& unreachable: _unreachables)
|
||||
{
|
||||
map<size_t, vector<YulString>> suitableCandidates;
|
||||
size_t neededSlots = unreachable.deficit;
|
||||
for (auto varName: unreachable.variableChoices)
|
||||
{
|
||||
if (varsToEliminate.count(varName))
|
||||
--neededSlots;
|
||||
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
|
||||
if (!util::contains(suitableCandidates[*cost], varName))
|
||||
suitableCandidates[*cost].emplace_back(varName);
|
||||
}
|
||||
for (auto candidatesByCost: suitableCandidates)
|
||||
{
|
||||
for (auto candidate: candidatesByCost.second)
|
||||
if (neededSlots--)
|
||||
varsToEliminate.emplace(candidate);
|
||||
else
|
||||
break;
|
||||
if (!neededSlots)
|
||||
break;
|
||||
}
|
||||
}
|
||||
Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true);
|
||||
UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool StackCompressor::run(
|
||||
@@ -176,39 +227,66 @@ bool StackCompressor::run(
|
||||
_object.code->statements.size() > 0 && holds_alternative<Block>(_object.code->statements.at(0)),
|
||||
"Need to run the function grouper before the stack compressor."
|
||||
);
|
||||
bool usesOptimizedCodeGenerator = false;
|
||||
if (auto evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
||||
usesOptimizedCodeGenerator =
|
||||
_optimizeStackAllocation &&
|
||||
evmDialect->evmVersion().canOverchargeGasForCall() &&
|
||||
evmDialect->providesObjectAccess();
|
||||
bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code);
|
||||
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
|
||||
if (usesOptimizedCodeGenerator)
|
||||
{
|
||||
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
|
||||
if (stackSurplus.empty())
|
||||
return true;
|
||||
|
||||
if (stackSurplus.count(YulString{}))
|
||||
{
|
||||
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
std::get<Block>(_object.code->statements.at(0)),
|
||||
static_cast<size_t>(stackSurplus.at({})),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
}
|
||||
|
||||
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
|
||||
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
|
||||
Block& mainBlock = std::get<Block>(_object.code->statements.at(0));
|
||||
if (
|
||||
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{});
|
||||
!stackTooDeepErrors.empty()
|
||||
)
|
||||
eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, allowMSizeOptimzation);
|
||||
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
||||
{
|
||||
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
||||
if (!stackSurplus.count(fun.name))
|
||||
continue;
|
||||
|
||||
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
fun,
|
||||
static_cast<size_t>(stackSurplus.at(fun.name)),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
if (
|
||||
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name);
|
||||
!stackTooDeepErrors.empty()
|
||||
)
|
||||
eliminateVariables(_dialect, fun.body, stackTooDeepErrors, allowMSizeOptimzation);
|
||||
}
|
||||
}
|
||||
else
|
||||
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
|
||||
{
|
||||
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
|
||||
if (stackSurplus.empty())
|
||||
return true;
|
||||
|
||||
if (stackSurplus.count(YulString{}))
|
||||
{
|
||||
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
std::get<Block>(_object.code->statements.at(0)),
|
||||
static_cast<size_t>(stackSurplus.at({})),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
||||
{
|
||||
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
||||
if (!stackSurplus.count(fun.name))
|
||||
continue;
|
||||
|
||||
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
fun,
|
||||
static_cast<size_t>(stackSurplus.at(fun.name)),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,21 @@
|
||||
#include <libyul/optimiser/StackLimitEvader.h>
|
||||
#include <libyul/optimiser/CallGraphGenerator.h>
|
||||
#include <libyul/optimiser/FunctionCallFinder.h>
|
||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||
#include <libyul/optimiser/NameDispenser.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/optimiser/StackToMemoryMover.h>
|
||||
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/CompilabilityChecker.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/view/concat.hpp>
|
||||
#include <range/v3/view/take.hpp>
|
||||
|
||||
@@ -114,6 +118,45 @@ u256 literalArgumentValue(FunctionCall const& _call)
|
||||
}
|
||||
}
|
||||
|
||||
void StackLimitEvader::run(
|
||||
OptimiserStepContext& _context,
|
||||
Object& _object
|
||||
)
|
||||
{
|
||||
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
|
||||
yulAssert(
|
||||
evmDialect && evmDialect->providesObjectAccess(),
|
||||
"StackLimitEvader can only be run on objects using the EVMDialect with object access."
|
||||
);
|
||||
if (evmDialect && evmDialect->evmVersion().canOverchargeGasForCall())
|
||||
{
|
||||
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object);
|
||||
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, *_object.code);
|
||||
run(_context, _object, StackLayoutGenerator::reportStackTooDeep(*cfg));
|
||||
}
|
||||
else
|
||||
run(_context, _object, CompilabilityChecker{
|
||||
_context.dialect,
|
||||
_object,
|
||||
true
|
||||
}.unreachableVariables);
|
||||
|
||||
}
|
||||
|
||||
void StackLimitEvader::run(
|
||||
OptimiserStepContext& _context,
|
||||
Object& _object,
|
||||
map<YulString, vector<StackLayoutGenerator::StackTooDeep>> const& _stackTooDeepErrors
|
||||
)
|
||||
{
|
||||
map<YulString, set<YulString>> unreachableVariables;
|
||||
for (auto&& [function, stackTooDeepErrors]: _stackTooDeepErrors)
|
||||
// TODO: choose wisely.
|
||||
for (auto const& stackTooDeepError: stackTooDeepErrors)
|
||||
unreachableVariables[function] += stackTooDeepError.variableChoices | ranges::views::take(stackTooDeepError.deficit) | ranges::to<set<YulString>>;
|
||||
run(_context, _object, unreachableVariables);
|
||||
}
|
||||
|
||||
void StackLimitEvader::run(
|
||||
OptimiserStepContext& _context,
|
||||
Object& _object,
|
||||
@@ -150,7 +193,7 @@ void StackLimitEvader::run(
|
||||
if (_unreachableVariables.count(function))
|
||||
return;
|
||||
|
||||
map<YulString, FunctionDefinition const*> functionDefinitions = FunctionDefinitionCollector::run(*_object.code);
|
||||
map<YulString, FunctionDefinition const*> functionDefinitions = allFunctionDefinitions(*_object.code);
|
||||
|
||||
MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls, functionDefinitions};
|
||||
uint64_t requiredSlots = memoryOffsetAllocator.run();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/backends/evm/StackLayoutGenerator.h>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
@@ -61,6 +62,25 @@ public:
|
||||
Object& _object,
|
||||
std::map<YulString, std::set<YulString>> const& _unreachableVariables
|
||||
);
|
||||
/// @a _stackTooDeepErrors can be determined by the StackLayoutGenerator.
|
||||
/// Can only be run on the EVM dialect with objects.
|
||||
/// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls
|
||||
/// with non-matching arguments are found, or if any of the @a _stackTooDeepErrors
|
||||
/// are contained in a recursive function.
|
||||
static void run(
|
||||
OptimiserStepContext& _context,
|
||||
Object& _object,
|
||||
std::map<YulString, std::vector<StackLayoutGenerator::StackTooDeep>> const& _stackTooDeepErrors
|
||||
);
|
||||
/// Determines stack too deep errors using the appropriate code generation backend.
|
||||
/// Can only be run on the EVM dialect with objects.
|
||||
/// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls
|
||||
/// with non-matching arguments are found, or if any of the unreachable variables
|
||||
/// are contained in a recursive function.
|
||||
static void run(
|
||||
OptimiserStepContext& _context,
|
||||
Object& _object
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <libyul/optimiser/StackToMemoryMover.h>
|
||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/optimiser/NameDispenser.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
@@ -87,7 +87,7 @@ void StackToMemoryMover::run(
|
||||
_context,
|
||||
memoryOffsetTracker,
|
||||
util::applyMap(
|
||||
FunctionDefinitionCollector::run(_block),
|
||||
allFunctionDefinitions(_block),
|
||||
util::mapTuple([](YulString _name, FunctionDefinition const* _funDef) {
|
||||
return make_pair(_name, _funDef->returnVariables);
|
||||
}),
|
||||
|
||||
+31
-14
@@ -95,6 +95,12 @@ void OptimiserSuite::run(
|
||||
set<YulString> const& _externallyUsedIdentifiers
|
||||
)
|
||||
{
|
||||
EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect);
|
||||
bool usesOptimizedCodeGenerator =
|
||||
_optimizeStackAllocation &&
|
||||
evmDialect &&
|
||||
evmDialect->evmVersion().canOverchargeGasForCall() &&
|
||||
evmDialect->providesObjectAccess();
|
||||
set<YulString> reservedIdentifiers = _externallyUsedIdentifiers;
|
||||
reservedIdentifiers += _dialect.fixedFunctionNames();
|
||||
|
||||
@@ -105,7 +111,10 @@ void OptimiserSuite::run(
|
||||
)(*_object.code));
|
||||
Block& ast = *_object.code;
|
||||
|
||||
OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast, _expectedExecutionsPerDeployment);
|
||||
NameDispenser dispenser{_dialect, ast, reservedIdentifiers};
|
||||
OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment};
|
||||
|
||||
OptimiserSuite suite(context, Debug::None);
|
||||
|
||||
// Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and
|
||||
// ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely.
|
||||
@@ -121,24 +130,32 @@ void OptimiserSuite::run(
|
||||
|
||||
// We ignore the return value because we will get a much better error
|
||||
// message once we perform code generation.
|
||||
StackCompressor::run(
|
||||
_dialect,
|
||||
_object,
|
||||
_optimizeStackAllocation,
|
||||
stackCompressorMaxIterations
|
||||
);
|
||||
if (!usesOptimizedCodeGenerator)
|
||||
StackCompressor::run(
|
||||
_dialect,
|
||||
_object,
|
||||
_optimizeStackAllocation,
|
||||
stackCompressorMaxIterations
|
||||
);
|
||||
suite.runSequence("fDnTOc g", ast);
|
||||
|
||||
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
||||
if (evmDialect)
|
||||
{
|
||||
yulAssert(_meter, "");
|
||||
ConstantOptimiser{*dialect, *_meter}(ast);
|
||||
if (dialect->providesObjectAccess() && _optimizeStackAllocation)
|
||||
StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{
|
||||
ConstantOptimiser{*evmDialect, *_meter}(ast);
|
||||
if (usesOptimizedCodeGenerator)
|
||||
{
|
||||
StackCompressor::run(
|
||||
_dialect,
|
||||
_object,
|
||||
_optimizeStackAllocation
|
||||
}.unreachableVariables);
|
||||
_optimizeStackAllocation,
|
||||
stackCompressorMaxIterations
|
||||
);
|
||||
if (evmDialect->providesObjectAccess())
|
||||
StackLimitEvader::run(suite.m_context, _object);
|
||||
}
|
||||
else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation)
|
||||
StackLimitEvader::run(suite.m_context, _object);
|
||||
}
|
||||
else if (dynamic_cast<WasmDialect const*>(&_dialect))
|
||||
{
|
||||
@@ -148,7 +165,7 @@ void OptimiserSuite::run(
|
||||
ast.statements.erase(ast.statements.begin());
|
||||
}
|
||||
|
||||
suite.m_dispenser.reset(ast);
|
||||
dispenser.reset(ast);
|
||||
NameSimplifier::run(suite.m_context, ast);
|
||||
VarNameCleaner::run(suite.m_context, ast);
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ public:
|
||||
PrintStep,
|
||||
PrintChanges
|
||||
};
|
||||
OptimiserSuite(OptimiserStepContext& _context, Debug _debug = Debug::None): m_context(_context), m_debug(_debug) {}
|
||||
|
||||
/// The value nullopt for `_expectedExecutionsPerDeployment` represents creation code.
|
||||
static void run(
|
||||
Dialect const& _dialect,
|
||||
@@ -82,20 +84,7 @@ public:
|
||||
static std::map<char, std::string> const& stepAbbreviationToNameMap();
|
||||
|
||||
private:
|
||||
OptimiserSuite(
|
||||
Dialect const& _dialect,
|
||||
std::set<YulString> const& _externallyUsedIdentifiers,
|
||||
Debug _debug,
|
||||
Block& _ast,
|
||||
std::optional<size_t> expectedExecutionsPerDeployment
|
||||
):
|
||||
m_dispenser{_dialect, _ast, _externallyUsedIdentifiers},
|
||||
m_context{_dialect, m_dispenser, _externallyUsedIdentifiers, expectedExecutionsPerDeployment},
|
||||
m_debug(_debug)
|
||||
{}
|
||||
|
||||
NameDispenser m_dispenser;
|
||||
OptimiserStepContext m_context;
|
||||
OptimiserStepContext& m_context;
|
||||
Debug m_debug;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user