Use side-effects of user defined functions in evm code transform.

This commit is contained in:
chriseth 2021-10-13 17:52:08 +02:00
parent 4100a59cca
commit 5ba8c109f4
19 changed files with 182 additions and 122 deletions

View File

@ -8,6 +8,7 @@ Compiler Features:
* Commandline Interface: Add `--no-cbor-metadata` that skips CBOR metadata from getting appended at the end of the bytecode. * Commandline Interface: Add `--no-cbor-metadata` that skips CBOR metadata from getting appended at the end of the bytecode.
* Natspec: Add event Natspec inheritance for devdoc. * Natspec: Add event Natspec inheritance for devdoc.
* Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode. * Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode.
* Yul EVM Code Transform: Generate more optimal code for user-defined functions that always terminate a transaction. No return labels will be pushed for calls to functions that always terminate.
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string. * Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
* Language Server: Add basic document hover support. * Language Server: Add basic document hover support.
* Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``. * Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``.

View File

@ -148,6 +148,8 @@ struct CFG
/// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving /// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving
/// more intermediate function calls) that leads back to this very call. /// more intermediate function calls) that leads back to this very call.
bool recursive = false; bool recursive = false;
/// True, if the call can return.
bool canContinue = true;
}; };
struct Assignment struct Assignment
{ {
@ -210,10 +212,12 @@ struct CFG
{ {
std::shared_ptr<DebugData const> debugData; std::shared_ptr<DebugData const> debugData;
Scope::Function const& function; Scope::Function const& function;
FunctionDefinition const& functionDefinition;
BasicBlock* entry = nullptr; BasicBlock* entry = nullptr;
std::vector<VariableSlot> parameters; std::vector<VariableSlot> parameters;
std::vector<VariableSlot> returnVariables; std::vector<VariableSlot> returnVariables;
std::vector<BasicBlock*> exits; std::vector<BasicBlock*> exits;
bool canContinue = true;
}; };
/// The main entry point, i.e. the start of the outermost Yul block. /// The main entry point, i.e. the start of the outermost Yul block.

View File

@ -23,6 +23,7 @@
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libyul/Utilities.h> #include <libyul/Utilities.h>
#include <libyul/ControlFlowSideEffectsCollector.h>
#include <libsolutil/cxx20.h> #include <libsolutil/cxx20.h>
#include <libsolutil/Visitor.h> #include <libsolutil/Visitor.h>
@ -214,7 +215,8 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
auto result = std::make_unique<CFG>(); auto result = std::make_unique<CFG>();
result->entry = &result->makeBlock(debugDataOf(_block)); result->entry = &result->makeBlock(debugDataOf(_block));
ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect); ControlFlowSideEffectsCollector sideEffects(_dialect, _block);
ControlFlowGraphBuilder builder(*result, _analysisInfo, sideEffects.functionSideEffects(), _dialect);
builder.m_currentBlock = result->entry; builder.m_currentBlock = result->entry;
builder(_block); builder(_block);
@ -232,10 +234,12 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
ControlFlowGraphBuilder::ControlFlowGraphBuilder( ControlFlowGraphBuilder::ControlFlowGraphBuilder(
CFG& _graph, CFG& _graph,
AsmAnalysisInfo const& _analysisInfo, AsmAnalysisInfo const& _analysisInfo,
map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects,
Dialect const& _dialect Dialect const& _dialect
): ):
m_graph(_graph), m_graph(_graph),
m_info(_analysisInfo), m_info(_analysisInfo),
m_functionSideEffects(_functionSideEffects),
m_dialect(_dialect) m_dialect(_dialect)
{ {
} }
@ -285,10 +289,10 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
return VariableSlot{lookupVariable(_var.name), _var.debugData}; return VariableSlot{lookupVariable(_var.name), _var.debugData};
}) | ranges::to<vector<VariableSlot>>; }) | ranges::to<vector<VariableSlot>>;
yulAssert(m_currentBlock, ""); Stack input = visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size());
yulAssert(m_currentBlock);
m_currentBlock->operations.emplace_back(CFG::Operation{ m_currentBlock->operations.emplace_back(CFG::Operation{
// input std::move(input),
visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()),
// output // output
assignedVariables | ranges::to<Stack>, assignedVariables | ranges::to<Stack>,
// operation // operation
@ -297,7 +301,6 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
} }
void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
{ {
yulAssert(m_currentBlock, "");
std::visit(util::GenericVisitor{ std::visit(util::GenericVisitor{
[&](FunctionCall const& _call) { [&](FunctionCall const& _call) {
Stack const& output = visitFunctionCall(_call); Stack const& output = visitFunctionCall(_call);
@ -305,16 +308,6 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
}, },
[&](auto const&) { yulAssert(false, ""); } [&](auto const&) { yulAssert(false, ""); }
}, _exprStmt.expression); }, _exprStmt.expression);
// TODO: Ideally this would be done on the expression label and for all functions that always revert,
// not only for builtins.
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name))
if (builtin->controlFlowSideEffects.terminatesOrReverts())
{
m_currentBlock->exit = CFG::BasicBlock::Terminated{};
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
} }
void ControlFlowGraphBuilder::operator()(Block const& _block) void ControlFlowGraphBuilder::operator()(Block const& _block)
@ -331,7 +324,8 @@ void ControlFlowGraphBuilder::operator()(If const& _if)
{ {
auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body)); auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock)); auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock));
makeConditionalJump(debugDataOf(_if), std::visit(*this, *_if.condition), ifBranch, afterIf); StackSlot condition = std::visit(*this, *_if.condition);
makeConditionalJump(debugDataOf(_if), std::move(condition), ifBranch, afterIf);
m_currentBlock = &ifBranch; m_currentBlock = &ifBranch;
(*this)(_if.body); (*this)(_if.body);
jump(debugDataOf(_if.body), afterIf); jump(debugDataOf(_if.body), afterIf);
@ -349,8 +343,9 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch)
// Artificially generate: // Artificially generate:
// let <ghostVariable> := <switchExpression> // let <ghostVariable> := <switchExpression>
VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)}; VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)};
StackSlot expression = std::visit(*this, *_switch.expression);
m_currentBlock->operations.emplace_back(CFG::Operation{ m_currentBlock->operations.emplace_back(CFG::Operation{
Stack{std::visit(*this, *_switch.expression)}, Stack{std::move(expression)},
Stack{ghostVarSlot}, Stack{ghostVarSlot},
CFG::Assignment{_switch.debugData, {ghostVarSlot}} CFG::Assignment{_switch.debugData, {ghostVarSlot}}
}); });
@ -430,7 +425,8 @@ void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
else else
{ {
jump(debugDataOf(_loop.pre), loopCondition); jump(debugDataOf(_loop.pre), loopCondition);
makeConditionalJump(debugDataOf(*_loop.condition), std::visit(*this, *_loop.condition), loopBody, afterLoop); StackSlot condition = std::visit(*this, *_loop.condition);
makeConditionalJump(debugDataOf(*_loop.condition), std::move(condition), loopBody, afterLoop);
m_currentBlock = &loopBody; m_currentBlock = &loopBody;
(*this)(_loop.body); (*this)(_loop.body);
jump(debugDataOf(_loop.body), post); jump(debugDataOf(_loop.body), post);
@ -473,7 +469,7 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function); CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function);
ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect}; ControlFlowGraphBuilder builder{m_graph, m_info, m_functionSideEffects, m_dialect};
builder.m_currentFunction = &functionInfo; builder.m_currentFunction = &functionInfo;
builder.m_currentBlock = functionInfo.entry; builder.m_currentBlock = functionInfo.entry;
builder(_function.body); builder(_function.body);
@ -481,33 +477,35 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo}; builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
} }
void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _function) void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _functionDefinition)
{ {
yulAssert(m_scope, ""); yulAssert(m_scope, "");
yulAssert(m_scope->identifiers.count(_function.name), ""); yulAssert(m_scope->identifiers.count(_functionDefinition.name), "");
Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name)); Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name));
yulAssert(m_info.scopes.at(&_function.body), ""); yulAssert(m_info.scopes.at(&_functionDefinition.body), "");
Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_functionDefinition).get()).get();
yulAssert(virtualFunctionScope, ""); yulAssert(virtualFunctionScope, "");
bool 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, _functionDefinition.debugData,
function, function,
&m_graph.makeBlock(debugDataOf(_function.body)), _functionDefinition,
_function.parameters | ranges::views::transform([&](auto const& _param) { &m_graph.makeBlock(debugDataOf(_functionDefinition.body)),
_functionDefinition.parameters | ranges::views::transform([&](auto const& _param) {
return VariableSlot{ return VariableSlot{
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)), std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)),
_param.debugData _param.debugData
}; };
}) | ranges::to<vector>, }) | ranges::to<vector>,
_function.returnVariables | ranges::views::transform([&](auto const& _retVar) { _functionDefinition.returnVariables | ranges::views::transform([&](auto const& _retVar) {
return VariableSlot{ return VariableSlot{
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)), std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)),
_retVar.debugData _retVar.debugData
}; };
}) | ranges::to<vector>, }) | ranges::to<vector>,
{} {},
m_functionSideEffects.at(&_functionDefinition).canContinue
})).second; })).second;
yulAssert(inserted); yulAssert(inserted);
} }
@ -517,6 +515,8 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
yulAssert(m_scope, ""); yulAssert(m_scope, "");
yulAssert(m_currentBlock, ""); yulAssert(m_currentBlock, "");
Stack const* output = nullptr;
bool canContinue = true;
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
{ {
Stack inputs; Stack inputs;
@ -524,7 +524,7 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
if (!builtin->literalArgument(idx).has_value()) if (!builtin->literalArgument(idx).has_value())
inputs.emplace_back(std::visit(*this, arg)); inputs.emplace_back(std::visit(*this, arg));
CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()}; CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()};
return m_currentBlock->operations.emplace_back(CFG::Operation{ output = &m_currentBlock->operations.emplace_back(CFG::Operation{
// input // input
std::move(inputs), std::move(inputs),
// output // output
@ -534,14 +534,18 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
// operation // operation
std::move(builtinCall) std::move(builtinCall)
}).output; }).output;
canContinue = builtin->controlFlowSideEffects.canContinue;
} }
else else
{ {
Scope::Function const& function = lookupFunction(_call.functionName.name); Scope::Function const& function = lookupFunction(_call.functionName.name);
Stack inputs{FunctionCallReturnLabelSlot{_call}}; canContinue = m_graph.functionInfo.at(&function).canContinue;
Stack inputs;
if (canContinue)
inputs.emplace_back(FunctionCallReturnLabelSlot{_call});
for (auto const& arg: _call.arguments | ranges::views::reverse) for (auto const& arg: _call.arguments | ranges::views::reverse)
inputs.emplace_back(std::visit(*this, arg)); inputs.emplace_back(std::visit(*this, arg));
return m_currentBlock->operations.emplace_back(CFG::Operation{ output = &m_currentBlock->operations.emplace_back(CFG::Operation{
// input // input
std::move(inputs), std::move(inputs),
// output // output
@ -549,9 +553,15 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
return TemporarySlot{_call, _i}; return TemporarySlot{_call, _i};
}) | ranges::to<Stack>, }) | ranges::to<Stack>,
// operation // operation
CFG::FunctionCall{_call.debugData, function, _call} CFG::FunctionCall{_call.debugData, function, _call, /* recursive */ false, canContinue}
}).output; }).output;
} }
if (!canContinue)
{
m_currentBlock->exit = CFG::BasicBlock::Terminated{};
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
return *output;
} }
Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount) Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount)

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <libyul/backends/evm/ControlFlowGraph.h> #include <libyul/backends/evm/ControlFlowGraph.h>
#include <libyul/ControlFlowSideEffects.h>
namespace solidity::yul namespace solidity::yul
{ {
@ -55,6 +56,7 @@ private:
ControlFlowGraphBuilder( ControlFlowGraphBuilder(
CFG& _graph, CFG& _graph,
AsmAnalysisInfo const& _analysisInfo, AsmAnalysisInfo const& _analysisInfo,
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects,
Dialect const& _dialect Dialect const& _dialect
); );
void registerFunction(FunctionDefinition const& _function); void registerFunction(FunctionDefinition const& _function);
@ -77,6 +79,7 @@ private:
); );
CFG& m_graph; CFG& m_graph;
AsmAnalysisInfo const& m_info; AsmAnalysisInfo const& m_info;
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& m_functionSideEffects;
Dialect const& m_dialect; Dialect const& m_dialect;
CFG::BasicBlock* m_currentBlock = nullptr; CFG::BasicBlock* m_currentBlock = nullptr;
Scope* m_scope = nullptr; Scope* m_scope = nullptr;

View File

@ -71,7 +71,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
// Validate stack. // Validate stack.
{ {
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), ""); yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
yulAssert(m_stack.size() >= _call.function.get().arguments.size() + 1, ""); yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0), "");
// Assert that we got the correct arguments on stack for the call. // Assert that we got the correct arguments on stack for the call.
for (auto&& [arg, slot]: ranges::zip_view( for (auto&& [arg, slot]: ranges::zip_view(
_call.functionCall.get().arguments | ranges::views::reverse, _call.functionCall.get().arguments | ranges::views::reverse,
@ -79,10 +79,13 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
)) ))
validateSlot(slot, arg); validateSlot(slot, arg);
// Assert that we got the correct return label on stack. // Assert that we got the correct return label on stack.
auto const* returnLabelSlot = get_if<FunctionCallReturnLabelSlot>( if (_call.canContinue)
&m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) {
); auto const* returnLabelSlot = get_if<FunctionCallReturnLabelSlot>(
yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), ""); &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1)
);
yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), "");
}
} }
// Emit code. // Emit code.
@ -90,16 +93,17 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
m_assembly.setSourceLocation(originLocationOf(_call)); m_assembly.setSourceLocation(originLocationOf(_call));
m_assembly.appendJumpTo( m_assembly.appendJumpTo(
getFunctionLabel(_call.function), getFunctionLabel(_call.function),
static_cast<int>(_call.function.get().returns.size() - _call.function.get().arguments.size()) - 1, static_cast<int>(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0),
AbstractAssembly::JumpType::IntoFunction AbstractAssembly::JumpType::IntoFunction
); );
m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get())); if (_call.canContinue)
m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get()));
} }
// Update stack. // Update stack.
{ {
// Remove arguments and return label from m_stack. // Remove arguments and return label from m_stack.
for (size_t i = 0; i < _call.function.get().arguments.size() + 1; ++i) for (size_t i = 0; i < _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0); ++i)
m_stack.pop_back(); m_stack.pop_back();
// Push return values to m_stack. // Push return values to m_stack.
for (size_t index: ranges::views::iota(0u, _call.function.get().returns.size())) for (size_t index: ranges::views::iota(0u, _call.function.get().returns.size()))
@ -479,8 +483,9 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
}, },
[&](CFG::BasicBlock::FunctionReturn const& _functionReturn) [&](CFG::BasicBlock::FunctionReturn const& _functionReturn)
{ {
yulAssert(m_currentFunctionInfo, ""); yulAssert(m_currentFunctionInfo);
yulAssert(m_currentFunctionInfo == _functionReturn.info, ""); yulAssert(m_currentFunctionInfo == _functionReturn.info);
yulAssert(m_currentFunctionInfo->canContinue);
// Construct the function return layout, which is fully determined by the function signature. // Construct the function return layout, which is fully determined by the function signature.
Stack exitStack = m_currentFunctionInfo->returnVariables | ranges::views::transform([](auto const& _varSlot){ Stack exitStack = m_currentFunctionInfo->returnVariables | ranges::views::transform([](auto const& _varSlot){
@ -494,11 +499,13 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
}, },
[&](CFG::BasicBlock::Terminated const&) [&](CFG::BasicBlock::Terminated const&)
{ {
// Assert that the last builtin call was in fact terminating. yulAssert(!_block.operations.empty());
yulAssert(!_block.operations.empty(), ""); if (CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation))
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation); yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
yulAssert(builtinCall, ""); else if (CFG::FunctionCall const* functionCall = get_if<CFG::FunctionCall>(&_block.operations.back().operation))
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), ""); yulAssert(!functionCall->canContinue);
else
yulAssert(false);
} }
}, _block.exit); }, _block.exit);
// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump. // TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.
@ -515,7 +522,8 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionInfo const& _functionInf
yulAssert(m_stack.empty() && m_assembly.stackHeight() == 0, ""); yulAssert(m_stack.empty() && m_assembly.stackHeight() == 0, "");
// Create function entry layout in m_stack. // Create function entry layout in m_stack.
m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function}); if (_functionInfo.canContinue)
m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function});
for (auto const& param: _functionInfo.parameters | ranges::views::reverse) for (auto const& param: _functionInfo.parameters | ranges::views::reverse)
m_stack.emplace_back(param); m_stack.emplace_back(param);
m_assembly.setStackHeight(static_cast<int>(m_stack.size())); m_assembly.setStackHeight(static_cast<int>(m_stack.size()));

View File

@ -54,7 +54,7 @@ StackLayout StackLayoutGenerator::run(CFG const& _cfg)
StackLayoutGenerator{stackLayout}.processEntryPoint(*_cfg.entry); StackLayoutGenerator{stackLayout}.processEntryPoint(*_cfg.entry);
for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry); StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry, &functionInfo);
return stackLayout; return stackLayout;
} }
@ -331,7 +331,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba
return stack; return stack;
} }
void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry) void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry, CFG::FunctionInfo const* _functionInfo)
{ {
list<CFG::BasicBlock const*> toVisit{&_entry}; list<CFG::BasicBlock const*> toVisit{&_entry};
set<CFG::BasicBlock const*> visited; set<CFG::BasicBlock const*> visited;
@ -402,7 +402,7 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry)
} }
stitchConditionalJumps(_entry); stitchConditionalJumps(_entry);
fillInJunk(_entry); fillInJunk(_entry, _functionInfo);
} }
optional<Stack> StackLayoutGenerator::getExitLayoutOrStageDependencies( optional<Stack> StackLayoutGenerator::getExitLayoutOrStageDependencies(
@ -707,7 +707,7 @@ Stack StackLayoutGenerator::compressStack(Stack _stack)
return _stack; return _stack;
} }
void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block) void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo)
{ {
/// Recursively adds junk to the subgraph starting on @a _entry. /// Recursively adds junk to the subgraph starting on @a _entry.
/// Since it is only called on cut-vertices, the full subgraph retains proper stack balance. /// Since it is only called on cut-vertices, the full subgraph retains proper stack balance.
@ -767,10 +767,56 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block)
createStackLayout(_source, _target, swap, dupOrPush, pop); createStackLayout(_source, _target, swap, dupOrPush, pop);
return opGas; return opGas;
}; };
/// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from
/// @a _entryLayout to @a _targetLayout.
auto getBestNumJunk = [&](Stack const& _entryLayout, Stack const& _targetLayout) -> size_t {
size_t bestCost = evaluateTransform(_entryLayout, _targetLayout);
size_t bestNumJunk = 0;
size_t maxJunk = _entryLayout.size();
for (size_t numJunk = 1; numJunk <= maxJunk; ++numJunk)
{
size_t cost = evaluateTransform(_entryLayout, Stack{numJunk, JunkSlot{}} + _targetLayout);
if (cost < bestCost)
{
bestCost = cost;
bestNumJunk = numJunk;
}
}
return bestNumJunk;
};
if (_functionInfo && !_functionInfo->canContinue && _block.allowsJunk())
{
size_t bestNumJunk = getBestNumJunk(
_functionInfo->parameters | ranges::views::reverse | ranges::to<Stack>,
m_layout.blockInfos.at(&_block).entryLayout
);
if (bestNumJunk > 0)
addJunkRecursive(&_block, bestNumJunk);
}
/// Traverses the CFG and at each block that allows junk, i.e. that is a cut-vertex that never leads to a function /// Traverses the CFG and at each block that allows junk, i.e. that is a cut-vertex that never leads to a function
/// return, checks if adding junk reduces the shuffling cost upon entering and if so recursively adds junk /// return, checks if adding junk reduces the shuffling cost upon entering and if so recursively adds junk
/// to the spanned subgraph. /// to the spanned subgraph.
util::BreadthFirstSearch<CFG::BasicBlock const*>{{&_block}}.run([&](CFG::BasicBlock const* _block, auto _addChild) { util::BreadthFirstSearch<CFG::BasicBlock const*>{{&_block}}.run([&](CFG::BasicBlock const* _block, auto _addChild) {
if (_block->allowsJunk())
{
auto& blockInfo = m_layout.blockInfos.at(_block);
Stack entryLayout = blockInfo.entryLayout;
Stack const& nextLayout = _block->operations.empty() ? blockInfo.exitLayout : m_layout.operationEntryLayout.at(&_block->operations.front());
if (entryLayout != nextLayout)
{
size_t bestNumJunk = getBestNumJunk(
entryLayout,
nextLayout
);
if (bestNumJunk > 0)
{
addJunkRecursive(_block, bestNumJunk);
blockInfo.entryLayout = entryLayout;
}
}
}
std::visit(util::GenericVisitor{ std::visit(util::GenericVisitor{
[&](CFG::BasicBlock::MainExit const&) {}, [&](CFG::BasicBlock::MainExit const&) {},
[&](CFG::BasicBlock::Jump const& _jump) [&](CFG::BasicBlock::Jump const& _jump)
@ -779,32 +825,6 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block)
}, },
[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
{ {
for (CFG::BasicBlock* exit: {_conditionalJump.zero, _conditionalJump.nonZero})
if (exit->allowsJunk())
{
auto& blockInfo = m_layout.blockInfos.at(exit);
Stack entryLayout = blockInfo.entryLayout;
Stack nextLayout = exit->operations.empty() ? blockInfo.exitLayout : m_layout.operationEntryLayout.at(&exit->operations.front());
size_t bestCost = evaluateTransform(entryLayout, nextLayout);
size_t bestNumJunk = 0;
size_t maxJunk = entryLayout.size();
for (size_t numJunk = 1; numJunk <= maxJunk; ++numJunk)
{
size_t cost = evaluateTransform(entryLayout, Stack{numJunk, JunkSlot{}} + nextLayout);
if (cost < bestCost)
{
bestCost = cost;
bestNumJunk = numJunk;
}
}
if (bestNumJunk > 0)
{
addJunkRecursive(exit, bestNumJunk);
blockInfo.entryLayout = entryLayout;
}
}
_addChild(_conditionalJump.zero); _addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero); _addChild(_conditionalJump.nonZero);
}, },

View File

@ -79,7 +79,7 @@ private:
/// Main algorithm walking the graph from entry to exit and propagating back the stack layouts to the entries. /// Main algorithm walking the graph from entry to exit and propagating back the stack layouts to the entries.
/// Iteratively reruns itself along backwards jumps until the layout is stabilized. /// Iteratively reruns itself along backwards jumps until the layout is stabilized.
void processEntryPoint(CFG::BasicBlock const& _entry); void processEntryPoint(CFG::BasicBlock const& _entry, CFG::FunctionInfo const* _functionInfo = nullptr);
/// @returns the best known exit layout of @a _block, if all dependencies are already @a _visited. /// @returns the best known exit layout of @a _block, if all dependencies are already @a _visited.
/// If not, adds the dependencies to @a _dependencyList and @returns std::nullopt. /// If not, adds the dependencies to @a _dependencyList and @returns std::nullopt.
@ -112,7 +112,7 @@ private:
static Stack compressStack(Stack _stack); static Stack compressStack(Stack _stack);
//// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper. //// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper.
void fillInJunk(CFG::BasicBlock const& _block); void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr);
StackLayout& m_layout; StackLayout& m_layout;
}; };

View File

@ -63,6 +63,6 @@ contract C {
// gas legacy: 414569 // gas legacy: 414569
// gas legacyOptimized: 319271 // gas legacyOptimized: 319271
// test_uint256() -> // test_uint256() ->
// gas irOptimized: 511919 // gas irOptimized: 511451
// gas legacy: 581876 // gas legacy: 581876
// gas legacyOptimized: 442757 // gas legacyOptimized: 442757

View File

@ -64,6 +64,6 @@ contract C {
// gas legacy: 414569 // gas legacy: 414569
// gas legacyOptimized: 319271 // gas legacyOptimized: 319271
// test_uint256() -> // test_uint256() ->
// gas irOptimized: 511919 // gas irOptimized: 511451
// gas legacy: 581876 // gas legacy: 581876
// gas legacyOptimized: 442757 // gas legacyOptimized: 442757

View File

@ -52,18 +52,18 @@ contract C {
// ---- // ----
// test_zeroed_indicies(uint256): 1 -> // test_zeroed_indicies(uint256): 1 ->
// test_zeroed_indicies(uint256): 5 -> // test_zeroed_indicies(uint256): 5 ->
// gas irOptimized: 132036 // gas irOptimized: 131998
// gas legacy: 132961 // gas legacy: 132961
// gas legacyOptimized: 130752 // gas legacyOptimized: 130752
// test_zeroed_indicies(uint256): 10 -> // test_zeroed_indicies(uint256): 10 ->
// gas irOptimized: 226094 // gas irOptimized: 226012
// gas legacy: 228071 // gas legacy: 228071
// gas legacyOptimized: 224010 // gas legacyOptimized: 224010
// test_zeroed_indicies(uint256): 15 -> // test_zeroed_indicies(uint256): 15 ->
// gas irOptimized: 324266 // gas irOptimized: 324140
// gas legacy: 327311 // gas legacy: 327311
// gas legacyOptimized: 321462 // gas legacyOptimized: 321462
// test_zeroed_indicies(uint256): 0xFF -> // test_zeroed_indicies(uint256): 0xFF ->
// gas irOptimized: 5122626 // gas irOptimized: 5120200
// gas legacy: 5172987 // gas legacy: 5172987
// gas legacyOptimized: 5066462 // gas legacyOptimized: 5066462

View File

@ -15,7 +15,7 @@ contract c {
} }
// ---- // ----
// test() -> // test() ->
// gas irOptimized: 114023 // gas irOptimized: 113892
// gas legacy: 131544 // gas legacy: 131544
// gas legacyOptimized: 126811 // gas legacyOptimized: 126811
// storageEmpty -> 1 // storageEmpty -> 1

View File

@ -28,7 +28,7 @@ contract C {
// compileViaYul: also // compileViaYul: also
// ---- // ----
// constructor() -> // constructor() ->
// gas irOptimized: 464753 // gas irOptimized: 449704
// gas legacy: 729908 // gas legacy: 729908
// gas legacyOptimized: 493347 // gas legacyOptimized: 493347
// h() -> 0x20, 0x40, 0x00, 0 // h() -> 0x20, 0x40, 0x00, 0

View File

@ -33,7 +33,7 @@ contract test {
// EVMVersion: >=constantinople // EVMVersion: >=constantinople
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 438376 // gas irOptimized: 414909
// gas legacy: 750723 // gas legacy: 750723
// gas legacyOptimized: 536620 // gas legacyOptimized: 536620
// encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0 // encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0
@ -51,10 +51,10 @@ contract test {
// encode_no_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE=" // encode_no_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE="
// encode_no_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy" // encode_no_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy"
// encode_inline_asm_large() // encode_inline_asm_large()
// gas irOptimized: 1387039 // gas irOptimized: 1374039
// gas legacy: 1688033 // gas legacy: 1688033
// gas legacyOptimized: 1205033 // gas legacyOptimized: 1205033
// encode_no_asm_large() // encode_no_asm_large()
// gas irOptimized: 3316107 // gas irOptimized: 3291100
// gas legacy: 4765077 // gas legacy: 4765077
// gas legacyOptimized: 2908077 // gas legacyOptimized: 2908077

View File

@ -294,11 +294,11 @@ contract Test {
// f() -> true // f() -> true
// g() -> true // g() -> true
// pair() -> true // pair() -> true
// gas irOptimized: 270080 // gas irOptimized: 269938
// gas legacy: 275952 // gas legacy: 275952
// gas legacyOptimized: 267239 // gas legacyOptimized: 267239
// verifyTx() -> true // verifyTx() -> true
// ~ emit Verified(string): 0x20, 0x16, "Successfully verified." // ~ emit Verified(string): 0x20, 0x16, "Successfully verified."
// gas irOptimized: 784027 // gas irOptimized: 783501
// gas legacy: 805423 // gas legacy: 805423
// gas legacyOptimized: 772571 // gas legacyOptimized: 772571

View File

@ -49,7 +49,7 @@ contract test {
} }
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 670586 // gas irOptimized: 642624
// gas legacy: 1096108 // gas legacy: 1096108
// gas legacyOptimized: 741962 // gas legacyOptimized: 741962
// toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0 // toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0
@ -69,6 +69,6 @@ contract test {
// gas legacy: 31621 // gas legacy: 31621
// gas legacyOptimized: 27914 // gas legacyOptimized: 27914
// benchmark(string,bytes32): 0x40, 0x0842021, 8, "solidity" -> 0x2020 // benchmark(string,bytes32): 0x40, 0x0842021, 8, "solidity" -> 0x2020
// gas irOptimized: 2017770 // gas irOptimized: 1989966
// gas legacy: 4294552 // gas legacy: 4294552
// gas legacyOptimized: 2327981 // gas legacyOptimized: 2327981

View File

@ -26,7 +26,7 @@ contract C {
// revertStrings: debug // revertStrings: debug
// ---- // ----
// constructor(), 1 ether -> // constructor(), 1 ether ->
// gas irOptimized: 424088 // gas irOptimized: 391081
// gas legacy: 823681 // gas legacy: 823681
// gas legacyOptimized: 505900 // gas legacyOptimized: 505900
// f(uint256): 0 -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code" // f(uint256): 0 -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code"

View File

@ -0,0 +1,22 @@
{
let b := f(1, 2)
function f(a, r) -> t { revert(0, 0) }
b := f(3, 4)
}
// ====
// stackOptimization: true
// ----
// /* "":17:18 */
// 0x02
// /* "":14:15 */
// 0x01
// /* "":12:19 */
// tag_1
// jump // in
// /* "":21:59 */
// tag_1:
// /* "":55:56 */
// 0x00
// /* "":45:57 */
// dup1
// revert

View File

@ -28,10 +28,9 @@
// Block0 [label="\ // Block0 [label="\
// i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ]\l\ // i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ]\l\
// Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ]\l\ // Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ]\l\
// h: [ RET[h] x ] => [ ]\l\ // h: [ x ] => [ ]\l\
// h: [ RET[h] y ] => [ ]\l\
// "]; // "];
// Block0Exit [label="MainExit"]; // Block0Exit [label="Terminated"];
// Block0 -> Block0Exit; // Block0 -> Block0Exit;
// //
// FunctionEntry_f_1 [label="function f(a, b) -> r"]; // FunctionEntry_f_1 [label="function f(a, b) -> r"];
@ -57,10 +56,9 @@
// FunctionEntry_h_3 -> Block3; // FunctionEntry_h_3 -> Block3;
// Block3 [label="\ // Block3 [label="\
// f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ]\l\ // f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ]\l\
// h: [ RET[h] TMP[f, 0] ] => [ ]\l\ // h: [ TMP[f, 0] ] => [ ]\l\
// g: [ RET[g] ] => [ ]\l\
// "]; // "];
// Block3Exit [label="FunctionReturn[h]"]; // Block3Exit [label="Terminated"];
// Block3 -> Block3Exit; // Block3 -> Block3Exit;
// //
// FunctionEntry_i_4 [label="function i() -> v, w"]; // FunctionEntry_i_4 [label="function i() -> v, w"];

View File

@ -27,21 +27,18 @@
// Entry -> Block0; // Entry -> Block0;
// Block0 [label="\ // Block0 [label="\
// [ ]\l\ // [ ]\l\
// [ RET[h] RET[h] RET[i] ]\l\ // [ RET[i] ]\l\
// i\l\ // i\l\
// [ RET[h] RET[h] TMP[i, 0] TMP[i, 1] ]\l\ // [ TMP[i, 0] TMP[i, 1] ]\l\
// [ RET[h] RET[h] TMP[i, 0] TMP[i, 1] ]\l\ // [ TMP[i, 0] TMP[i, 1] ]\l\
// Assignment(x, y)\l\ // Assignment(x, y)\l\
// [ RET[h] RET[h] x y ]\l\ // [ x y ]\l\
// [ RET[h] y RET[h] x ]\l\ // [ x ]\l\
// h\l\
// [ RET[h] y ]\l\
// [ RET[h] y ]\l\
// h\l\ // h\l\
// [ ]\l\ // [ ]\l\
// [ ]\l\ // [ ]\l\
// "]; // "];
// Block0Exit [label="MainExit"]; // Block0Exit [label="Terminated"];
// Block0 -> Block0Exit; // Block0 -> Block0Exit;
// //
// FunctionEntry_f [label="function f(a, b) -> r\l\ // FunctionEntry_f [label="function f(a, b) -> r\l\
@ -83,19 +80,16 @@
// [ RET x ]"]; // [ RET x ]"];
// FunctionEntry_h -> Block3; // FunctionEntry_h -> Block3;
// Block3 [label="\ // Block3 [label="\
// [ RET RET[h] RET[f] 0x00 x ]\l\ // [ RET[f] 0x00 x ]\l\
// [ RET RET[h] RET[f] 0x00 x ]\l\ // [ RET[f] 0x00 x ]\l\
// f\l\ // f\l\
// [ RET RET[h] TMP[f, 0] ]\l\ // [ TMP[f, 0] ]\l\
// [ RET RET[h] TMP[f, 0] ]\l\ // [ TMP[f, 0] ]\l\
// h\l\ // h\l\
// [ RET ]\l\ // [ ]\l\
// [ RET RET[g] ]\l\ // [ ]\l\
// g\l\
// [ RET ]\l\
// [ RET ]\l\
// "]; // "];
// Block3Exit [label="FunctionReturn[h]"]; // Block3Exit [label="Terminated"];
// Block3 -> Block3Exit; // Block3 -> Block3Exit;
// //
// FunctionEntry_i [label="function i() -> v, w\l\ // FunctionEntry_i [label="function i() -> v, w\l\