Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth
2021-11-09 18:26:34 +01:00
409 changed files with 4885 additions and 3597 deletions
+2 -2
View File
@@ -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
+60 -50
View File
@@ -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);
}
+24 -15
View File
@@ -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;
};
+60
View File
@@ -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();
}
@@ -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;
};
}
+32 -22
View File
@@ -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;
+32 -15
View File
@@ -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;
+30
View File
@@ -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);
}
}
+10 -1
View File
@@ -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
)
{
+7 -7
View File
@@ -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;
};
}
+10 -1
View File
@@ -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
)
{
+7 -6
View File
@@ -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;
};
}
+11 -15
View File
@@ -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);
+7 -2
View File
@@ -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())
+9 -2
View File
@@ -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);
}
+19 -7
View File
@@ -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;
}
+8 -14
View File
@@ -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);
}
+6 -16
View File
@@ -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);
}
+24 -5
View File
@@ -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;
}
+18 -8
View File
@@ -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;
};
}
+104 -26
View File
@@ -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;
}
+45 -2
View File
@@ -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();
+20
View File
@@ -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
);
};
}
+2 -2
View File
@@ -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
View File
@@ -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);
+3 -14
View File
@@ -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;
};