mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #12132 from ethereum/userDefinedSideEffectsCodeTransform
Use side-effects of user defined functions in evm code transform.
This commit is contained in:
commit
3109ce2dbc
@ -9,6 +9,7 @@ Compiler Features:
|
||||
* EVM: Basic support for the EVM version "Paris".
|
||||
* 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.
|
||||
* 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.
|
||||
* Language Server: Add basic document hover support.
|
||||
* Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``.
|
||||
|
@ -148,6 +148,8 @@ struct CFG
|
||||
/// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving
|
||||
/// more intermediate function calls) that leads back to this very call.
|
||||
bool recursive = false;
|
||||
/// True, if the call can return.
|
||||
bool canContinue = true;
|
||||
};
|
||||
struct Assignment
|
||||
{
|
||||
@ -210,10 +212,12 @@ struct CFG
|
||||
{
|
||||
std::shared_ptr<DebugData const> debugData;
|
||||
Scope::Function const& function;
|
||||
FunctionDefinition const& functionDefinition;
|
||||
BasicBlock* entry = nullptr;
|
||||
std::vector<VariableSlot> parameters;
|
||||
std::vector<VariableSlot> returnVariables;
|
||||
std::vector<BasicBlock*> exits;
|
||||
bool canContinue = true;
|
||||
};
|
||||
|
||||
/// The main entry point, i.e. the start of the outermost Yul block.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
|
||||
#include <libsolutil/cxx20.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
@ -214,7 +215,8 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
|
||||
auto result = std::make_unique<CFG>();
|
||||
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(_block);
|
||||
|
||||
@ -232,10 +234,12 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
|
||||
ControlFlowGraphBuilder::ControlFlowGraphBuilder(
|
||||
CFG& _graph,
|
||||
AsmAnalysisInfo const& _analysisInfo,
|
||||
map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects,
|
||||
Dialect const& _dialect
|
||||
):
|
||||
m_graph(_graph),
|
||||
m_info(_analysisInfo),
|
||||
m_functionSideEffects(_functionSideEffects),
|
||||
m_dialect(_dialect)
|
||||
{
|
||||
}
|
||||
@ -285,10 +289,10 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
|
||||
return VariableSlot{lookupVariable(_var.name), _var.debugData};
|
||||
}) | ranges::to<vector<VariableSlot>>;
|
||||
|
||||
yulAssert(m_currentBlock, "");
|
||||
Stack input = visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size());
|
||||
yulAssert(m_currentBlock);
|
||||
m_currentBlock->operations.emplace_back(CFG::Operation{
|
||||
// input
|
||||
visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()),
|
||||
std::move(input),
|
||||
// output
|
||||
assignedVariables | ranges::to<Stack>,
|
||||
// operation
|
||||
@ -297,7 +301,6 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
|
||||
}
|
||||
void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
||||
{
|
||||
yulAssert(m_currentBlock, "");
|
||||
std::visit(util::GenericVisitor{
|
||||
[&](FunctionCall const& _call) {
|
||||
Stack const& output = visitFunctionCall(_call);
|
||||
@ -305,16 +308,6 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
||||
},
|
||||
[&](auto const&) { yulAssert(false, ""); }
|
||||
}, _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)
|
||||
@ -331,7 +324,8 @@ void ControlFlowGraphBuilder::operator()(If const& _if)
|
||||
{
|
||||
auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
|
||||
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;
|
||||
(*this)(_if.body);
|
||||
jump(debugDataOf(_if.body), afterIf);
|
||||
@ -349,8 +343,9 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch)
|
||||
// Artificially generate:
|
||||
// let <ghostVariable> := <switchExpression>
|
||||
VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)};
|
||||
StackSlot expression = std::visit(*this, *_switch.expression);
|
||||
m_currentBlock->operations.emplace_back(CFG::Operation{
|
||||
Stack{std::visit(*this, *_switch.expression)},
|
||||
Stack{std::move(expression)},
|
||||
Stack{ghostVarSlot},
|
||||
CFG::Assignment{_switch.debugData, {ghostVarSlot}}
|
||||
});
|
||||
@ -430,7 +425,8 @@ void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
|
||||
else
|
||||
{
|
||||
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;
|
||||
(*this)(_loop.body);
|
||||
jump(debugDataOf(_loop.body), post);
|
||||
@ -473,7 +469,7 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _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_currentBlock = functionInfo.entry;
|
||||
builder(_function.body);
|
||||
@ -481,33 +477,35 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
|
||||
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->identifiers.count(_function.name), "");
|
||||
Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
|
||||
yulAssert(m_scope->identifiers.count(_functionDefinition.name), "");
|
||||
Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name));
|
||||
|
||||
yulAssert(m_info.scopes.at(&_function.body), "");
|
||||
Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
|
||||
yulAssert(m_info.scopes.at(&_functionDefinition.body), "");
|
||||
Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_functionDefinition).get()).get();
|
||||
yulAssert(virtualFunctionScope, "");
|
||||
|
||||
bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
|
||||
_function.debugData,
|
||||
_functionDefinition.debugData,
|
||||
function,
|
||||
&m_graph.makeBlock(debugDataOf(_function.body)),
|
||||
_function.parameters | ranges::views::transform([&](auto const& _param) {
|
||||
_functionDefinition,
|
||||
&m_graph.makeBlock(debugDataOf(_functionDefinition.body)),
|
||||
_functionDefinition.parameters | ranges::views::transform([&](auto const& _param) {
|
||||
return VariableSlot{
|
||||
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)),
|
||||
_param.debugData
|
||||
};
|
||||
}) | ranges::to<vector>,
|
||||
_function.returnVariables | ranges::views::transform([&](auto const& _retVar) {
|
||||
_functionDefinition.returnVariables | ranges::views::transform([&](auto const& _retVar) {
|
||||
return VariableSlot{
|
||||
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)),
|
||||
_retVar.debugData
|
||||
};
|
||||
}) | ranges::to<vector>,
|
||||
{}
|
||||
{},
|
||||
m_functionSideEffects.at(&_functionDefinition).canContinue
|
||||
})).second;
|
||||
yulAssert(inserted);
|
||||
}
|
||||
@ -517,6 +515,8 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
|
||||
yulAssert(m_scope, "");
|
||||
yulAssert(m_currentBlock, "");
|
||||
|
||||
Stack const* output = nullptr;
|
||||
bool canContinue = true;
|
||||
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
|
||||
{
|
||||
Stack inputs;
|
||||
@ -524,7 +524,7 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
|
||||
if (!builtin->literalArgument(idx).has_value())
|
||||
inputs.emplace_back(std::visit(*this, arg));
|
||||
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
|
||||
std::move(inputs),
|
||||
// output
|
||||
@ -534,14 +534,18 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
|
||||
// operation
|
||||
std::move(builtinCall)
|
||||
}).output;
|
||||
canContinue = builtin->controlFlowSideEffects.canContinue;
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
inputs.emplace_back(std::visit(*this, arg));
|
||||
return m_currentBlock->operations.emplace_back(CFG::Operation{
|
||||
output = &m_currentBlock->operations.emplace_back(CFG::Operation{
|
||||
// input
|
||||
std::move(inputs),
|
||||
// output
|
||||
@ -549,9 +553,15 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal
|
||||
return TemporarySlot{_call, _i};
|
||||
}) | ranges::to<Stack>,
|
||||
// operation
|
||||
CFG::FunctionCall{_call.debugData, function, _call}
|
||||
CFG::FunctionCall{_call.debugData, function, _call, /* recursive */ false, canContinue}
|
||||
}).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)
|
||||
|
@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/backends/evm/ControlFlowGraph.h>
|
||||
#include <libyul/ControlFlowSideEffects.h>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
@ -55,6 +56,7 @@ private:
|
||||
ControlFlowGraphBuilder(
|
||||
CFG& _graph,
|
||||
AsmAnalysisInfo const& _analysisInfo,
|
||||
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects,
|
||||
Dialect const& _dialect
|
||||
);
|
||||
void registerFunction(FunctionDefinition const& _function);
|
||||
@ -77,6 +79,7 @@ private:
|
||||
);
|
||||
CFG& m_graph;
|
||||
AsmAnalysisInfo const& m_info;
|
||||
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& m_functionSideEffects;
|
||||
Dialect const& m_dialect;
|
||||
CFG::BasicBlock* m_currentBlock = nullptr;
|
||||
Scope* m_scope = nullptr;
|
||||
|
@ -71,7 +71,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
|
||||
// Validate stack.
|
||||
{
|
||||
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.
|
||||
for (auto&& [arg, slot]: ranges::zip_view(
|
||||
_call.functionCall.get().arguments | ranges::views::reverse,
|
||||
@ -79,10 +79,13 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
|
||||
))
|
||||
validateSlot(slot, arg);
|
||||
// Assert that we got the correct return label on stack.
|
||||
auto const* returnLabelSlot = get_if<FunctionCallReturnLabelSlot>(
|
||||
&m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1)
|
||||
);
|
||||
yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), "");
|
||||
if (_call.canContinue)
|
||||
{
|
||||
auto const* returnLabelSlot = get_if<FunctionCallReturnLabelSlot>(
|
||||
&m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1)
|
||||
);
|
||||
yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), "");
|
||||
}
|
||||
}
|
||||
|
||||
// Emit code.
|
||||
@ -90,16 +93,17 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call)
|
||||
m_assembly.setSourceLocation(originLocationOf(_call));
|
||||
m_assembly.appendJumpTo(
|
||||
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
|
||||
);
|
||||
m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get()));
|
||||
if (_call.canContinue)
|
||||
m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get()));
|
||||
}
|
||||
|
||||
// Update 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();
|
||||
// Push return values to m_stack.
|
||||
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)
|
||||
{
|
||||
yulAssert(m_currentFunctionInfo, "");
|
||||
yulAssert(m_currentFunctionInfo == _functionReturn.info, "");
|
||||
yulAssert(m_currentFunctionInfo);
|
||||
yulAssert(m_currentFunctionInfo == _functionReturn.info);
|
||||
yulAssert(m_currentFunctionInfo->canContinue);
|
||||
|
||||
// Construct the function return layout, which is fully determined by the function signature.
|
||||
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&)
|
||||
{
|
||||
// Assert that the last builtin call was in fact terminating.
|
||||
yulAssert(!_block.operations.empty(), "");
|
||||
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
|
||||
yulAssert(builtinCall, "");
|
||||
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
|
||||
yulAssert(!_block.operations.empty());
|
||||
if (CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation))
|
||||
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
|
||||
else if (CFG::FunctionCall const* functionCall = get_if<CFG::FunctionCall>(&_block.operations.back().operation))
|
||||
yulAssert(!functionCall->canContinue);
|
||||
else
|
||||
yulAssert(false);
|
||||
}
|
||||
}, _block.exit);
|
||||
// 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, "");
|
||||
|
||||
// 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)
|
||||
m_stack.emplace_back(param);
|
||||
m_assembly.setStackHeight(static_cast<int>(m_stack.size()));
|
||||
|
@ -54,7 +54,7 @@ StackLayout StackLayoutGenerator::run(CFG const& _cfg)
|
||||
StackLayoutGenerator{stackLayout}.processEntryPoint(*_cfg.entry);
|
||||
|
||||
for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
|
||||
StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry);
|
||||
StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry, &functionInfo);
|
||||
|
||||
return stackLayout;
|
||||
}
|
||||
@ -331,7 +331,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba
|
||||
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};
|
||||
set<CFG::BasicBlock const*> visited;
|
||||
@ -402,7 +402,7 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry)
|
||||
}
|
||||
|
||||
stitchConditionalJumps(_entry);
|
||||
fillInJunk(_entry);
|
||||
fillInJunk(_entry, _functionInfo);
|
||||
}
|
||||
|
||||
optional<Stack> StackLayoutGenerator::getExitLayoutOrStageDependencies(
|
||||
@ -707,7 +707,7 @@ Stack StackLayoutGenerator::compressStack(Stack _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.
|
||||
/// 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);
|
||||
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
|
||||
/// return, checks if adding junk reduces the shuffling cost upon entering and if so recursively adds junk
|
||||
/// to the spanned subgraph.
|
||||
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{
|
||||
[&](CFG::BasicBlock::MainExit const&) {},
|
||||
[&](CFG::BasicBlock::Jump const& _jump)
|
||||
@ -779,32 +825,6 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block)
|
||||
},
|
||||
[&](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.nonZero);
|
||||
},
|
||||
|
@ -79,7 +79,7 @@ private:
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
/// If not, adds the dependencies to @a _dependencyList and @returns std::nullopt.
|
||||
@ -112,7 +112,7 @@ private:
|
||||
static Stack compressStack(Stack _stack);
|
||||
|
||||
//// 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;
|
||||
};
|
||||
|
@ -69,6 +69,9 @@ function zeppelin_test
|
||||
sed -i "s|it(\('reverts \)|it.skip(\1|g" math/SignedSafeMath.test.js
|
||||
sed -i "s|it(\('reverts \)|it.skip(\1|g" structs/EnumerableSet.behavior.js
|
||||
popd
|
||||
pushd test/proxy/
|
||||
sed -i "s|it(\('proxy admin cannot call delegated functions',\)|it.skip(\1|g" transparent/TransparentUpgradeableProxy.behaviour.js
|
||||
popd
|
||||
|
||||
|
||||
# In some cases Hardhat does not detect revert reasons properly via IR.
|
||||
|
@ -63,6 +63,6 @@ contract C {
|
||||
// gas legacy: 414569
|
||||
// gas legacyOptimized: 319271
|
||||
// test_uint256() ->
|
||||
// gas irOptimized: 511919
|
||||
// gas irOptimized: 511451
|
||||
// gas legacy: 581876
|
||||
// gas legacyOptimized: 442757
|
||||
|
@ -64,6 +64,6 @@ contract C {
|
||||
// gas legacy: 414569
|
||||
// gas legacyOptimized: 319271
|
||||
// test_uint256() ->
|
||||
// gas irOptimized: 511919
|
||||
// gas irOptimized: 511451
|
||||
// gas legacy: 581876
|
||||
// gas legacyOptimized: 442757
|
||||
|
@ -52,18 +52,18 @@ contract C {
|
||||
// ----
|
||||
// test_zeroed_indicies(uint256): 1 ->
|
||||
// test_zeroed_indicies(uint256): 5 ->
|
||||
// gas irOptimized: 132036
|
||||
// gas irOptimized: 131998
|
||||
// gas legacy: 132961
|
||||
// gas legacyOptimized: 130752
|
||||
// test_zeroed_indicies(uint256): 10 ->
|
||||
// gas irOptimized: 226094
|
||||
// gas irOptimized: 226012
|
||||
// gas legacy: 228071
|
||||
// gas legacyOptimized: 224010
|
||||
// test_zeroed_indicies(uint256): 15 ->
|
||||
// gas irOptimized: 324266
|
||||
// gas irOptimized: 324140
|
||||
// gas legacy: 327311
|
||||
// gas legacyOptimized: 321462
|
||||
// test_zeroed_indicies(uint256): 0xFF ->
|
||||
// gas irOptimized: 5122626
|
||||
// gas irOptimized: 5120200
|
||||
// gas legacy: 5172987
|
||||
// gas legacyOptimized: 5066462
|
||||
|
@ -15,7 +15,7 @@ contract c {
|
||||
}
|
||||
// ----
|
||||
// test() ->
|
||||
// gas irOptimized: 114023
|
||||
// gas irOptimized: 113892
|
||||
// gas legacy: 131544
|
||||
// gas legacyOptimized: 126811
|
||||
// storageEmpty -> 1
|
||||
|
@ -28,7 +28,7 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor() ->
|
||||
// gas irOptimized: 464753
|
||||
// gas irOptimized: 449704
|
||||
// gas legacy: 729908
|
||||
// gas legacyOptimized: 493347
|
||||
// h() -> 0x20, 0x40, 0x00, 0
|
||||
|
@ -33,7 +33,7 @@ contract test {
|
||||
// EVMVersion: >=constantinople
|
||||
// ----
|
||||
// constructor()
|
||||
// gas irOptimized: 438376
|
||||
// gas irOptimized: 414909
|
||||
// gas legacy: 750723
|
||||
// gas legacyOptimized: 536620
|
||||
// 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, 6, "foobar" -> 0x20, 8, "Zm9vYmFy"
|
||||
// encode_inline_asm_large()
|
||||
// gas irOptimized: 1387039
|
||||
// gas irOptimized: 1374039
|
||||
// gas legacy: 1688033
|
||||
// gas legacyOptimized: 1205033
|
||||
// encode_no_asm_large()
|
||||
// gas irOptimized: 3316107
|
||||
// gas irOptimized: 3291100
|
||||
// gas legacy: 4765077
|
||||
// gas legacyOptimized: 2908077
|
||||
|
@ -294,11 +294,11 @@ contract Test {
|
||||
// f() -> true
|
||||
// g() -> true
|
||||
// pair() -> true
|
||||
// gas irOptimized: 270080
|
||||
// gas irOptimized: 269938
|
||||
// gas legacy: 275952
|
||||
// gas legacyOptimized: 267239
|
||||
// verifyTx() -> true
|
||||
// ~ emit Verified(string): 0x20, 0x16, "Successfully verified."
|
||||
// gas irOptimized: 784027
|
||||
// gas irOptimized: 783501
|
||||
// gas legacy: 805423
|
||||
// gas legacyOptimized: 772571
|
||||
|
@ -49,7 +49,7 @@ contract test {
|
||||
}
|
||||
// ----
|
||||
// constructor()
|
||||
// gas irOptimized: 670586
|
||||
// gas irOptimized: 642624
|
||||
// gas legacy: 1096108
|
||||
// gas legacyOptimized: 741962
|
||||
// toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0
|
||||
@ -69,6 +69,6 @@ contract test {
|
||||
// gas legacy: 31621
|
||||
// gas legacyOptimized: 27914
|
||||
// benchmark(string,bytes32): 0x40, 0x0842021, 8, "solidity" -> 0x2020
|
||||
// gas irOptimized: 2017770
|
||||
// gas irOptimized: 1989966
|
||||
// gas legacy: 4294552
|
||||
// gas legacyOptimized: 2327981
|
||||
|
@ -26,7 +26,7 @@ contract C {
|
||||
// revertStrings: debug
|
||||
// ----
|
||||
// constructor(), 1 ether ->
|
||||
// gas irOptimized: 424088
|
||||
// gas irOptimized: 391081
|
||||
// gas legacy: 823681
|
||||
// gas legacyOptimized: 505900
|
||||
// f(uint256): 0 -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code"
|
||||
|
22
test/libyul/evmCodeTransform/after_terminating_function.yul
Normal file
22
test/libyul/evmCodeTransform/after_terminating_function.yul
Normal 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
|
@ -28,10 +28,9 @@
|
||||
// Block0 [label="\
|
||||
// i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ]\l\
|
||||
// Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ]\l\
|
||||
// h: [ RET[h] x ] => [ ]\l\
|
||||
// h: [ RET[h] y ] => [ ]\l\
|
||||
// h: [ x ] => [ ]\l\
|
||||
// "];
|
||||
// Block0Exit [label="MainExit"];
|
||||
// Block0Exit [label="Terminated"];
|
||||
// Block0 -> Block0Exit;
|
||||
//
|
||||
// FunctionEntry_f_1 [label="function f(a, b) -> r"];
|
||||
@ -57,10 +56,9 @@
|
||||
// FunctionEntry_h_3 -> Block3;
|
||||
// Block3 [label="\
|
||||
// f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ]\l\
|
||||
// h: [ RET[h] TMP[f, 0] ] => [ ]\l\
|
||||
// g: [ RET[g] ] => [ ]\l\
|
||||
// h: [ TMP[f, 0] ] => [ ]\l\
|
||||
// "];
|
||||
// Block3Exit [label="FunctionReturn[h]"];
|
||||
// Block3Exit [label="Terminated"];
|
||||
// Block3 -> Block3Exit;
|
||||
//
|
||||
// FunctionEntry_i_4 [label="function i() -> v, w"];
|
||||
|
@ -27,21 +27,18 @@
|
||||
// Entry -> Block0;
|
||||
// Block0 [label="\
|
||||
// [ ]\l\
|
||||
// [ RET[h] RET[h] RET[i] ]\l\
|
||||
// [ RET[i] ]\l\
|
||||
// i\l\
|
||||
// [ RET[h] RET[h] 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\
|
||||
// [ TMP[i, 0] TMP[i, 1] ]\l\
|
||||
// Assignment(x, y)\l\
|
||||
// [ RET[h] RET[h] x y ]\l\
|
||||
// [ RET[h] y RET[h] x ]\l\
|
||||
// h\l\
|
||||
// [ RET[h] y ]\l\
|
||||
// [ RET[h] y ]\l\
|
||||
// [ x y ]\l\
|
||||
// [ x ]\l\
|
||||
// h\l\
|
||||
// [ ]\l\
|
||||
// [ ]\l\
|
||||
// "];
|
||||
// Block0Exit [label="MainExit"];
|
||||
// Block0Exit [label="Terminated"];
|
||||
// Block0 -> Block0Exit;
|
||||
//
|
||||
// FunctionEntry_f [label="function f(a, b) -> r\l\
|
||||
@ -83,19 +80,16 @@
|
||||
// [ RET x ]"];
|
||||
// FunctionEntry_h -> Block3;
|
||||
// Block3 [label="\
|
||||
// [ RET RET[h] RET[f] 0x00 x ]\l\
|
||||
// [ RET RET[h] RET[f] 0x00 x ]\l\
|
||||
// [ RET[f] 0x00 x ]\l\
|
||||
// [ RET[f] 0x00 x ]\l\
|
||||
// f\l\
|
||||
// [ RET RET[h] TMP[f, 0] ]\l\
|
||||
// [ RET RET[h] TMP[f, 0] ]\l\
|
||||
// [ TMP[f, 0] ]\l\
|
||||
// [ TMP[f, 0] ]\l\
|
||||
// h\l\
|
||||
// [ RET ]\l\
|
||||
// [ RET RET[g] ]\l\
|
||||
// g\l\
|
||||
// [ RET ]\l\
|
||||
// [ RET ]\l\
|
||||
// [ ]\l\
|
||||
// [ ]\l\
|
||||
// "];
|
||||
// Block3Exit [label="FunctionReturn[h]"];
|
||||
// Block3Exit [label="Terminated"];
|
||||
// Block3 -> Block3Exit;
|
||||
//
|
||||
// FunctionEntry_i [label="function i() -> v, w\l\
|
||||
|
Loading…
Reference in New Issue
Block a user