Merge pull request #12132 from ethereum/userDefinedSideEffectsCodeTransform

Use side-effects of user defined functions in evm code transform.
This commit is contained in:
Daniel 2022-11-22 14:26:57 +01:00 committed by GitHub
commit 3109ce2dbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 185 additions and 122 deletions

View File

@ -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))``.

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
/// 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.

View File

@ -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)

View File

@ -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;

View File

@ -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()));

View File

@ -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);
},

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.
/// 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;
};

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

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="\
// 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"];

View File

@ -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\