mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10942 from ethereum/returnSlotAllocation
Delayed return slot allocation.
This commit is contained in:
commit
2856f56525
@ -14,6 +14,7 @@ Compiler Features:
|
||||
* SMTChecker: Report out of bounds index access for arrays and fixed bytes.
|
||||
* Standard JSON: Model checker option ``settings.modelChecker.targets`` also accepts ``outOfBounds``.
|
||||
* Yul Optimizer: Added a new step FunctionSpecializer, that specializes a function with its literal arguments.
|
||||
* Yul EVM Code Transform: Stack Optimization: Reuse slots of unused function arguments and defer allocating stack slots for return variables until after expression statements and assignments that do not reference them.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -23,13 +23,17 @@
|
||||
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Utilities.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
|
||||
#include <range/v3/algorithm/max.hpp>
|
||||
#include <range/v3/algorithm/none_of.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
@ -103,7 +107,9 @@ CodeTransform::CodeTransform(
|
||||
BuiltinContext& _builtinContext,
|
||||
ExternalIdentifierAccess _identifierAccess,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
shared_ptr<Context> _context
|
||||
shared_ptr<Context> _context,
|
||||
vector<TypedName> _delayedReturnVariables,
|
||||
optional<AbstractAssembly::LabelID> _functionExitLabel
|
||||
):
|
||||
m_assembly(_assembly),
|
||||
m_info(_analysisInfo),
|
||||
@ -111,8 +117,10 @@ CodeTransform::CodeTransform(
|
||||
m_builtinContext(_builtinContext),
|
||||
m_allowStackOpt(_allowStackOpt),
|
||||
m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
|
||||
m_identifierAccess(std::move(_identifierAccess)),
|
||||
m_context(std::move(_context))
|
||||
m_identifierAccess(move(_identifierAccess)),
|
||||
m_context(move(_context)),
|
||||
m_delayedReturnVariables(move(_delayedReturnVariables)),
|
||||
m_functionExitLabel(_functionExitLabel)
|
||||
{
|
||||
if (!m_context)
|
||||
{
|
||||
@ -146,12 +154,18 @@ void CodeTransform::freeUnusedVariables(bool _popUnusedSlotsAtStackTop)
|
||||
return;
|
||||
|
||||
for (auto const& identifier: m_scope->identifiers)
|
||||
if (holds_alternative<Scope::Variable>(identifier.second))
|
||||
{
|
||||
Scope::Variable const& var = std::get<Scope::Variable>(identifier.second);
|
||||
if (m_variablesScheduledForDeletion.count(&var))
|
||||
deleteVariable(var);
|
||||
}
|
||||
if (Scope::Variable const* var = get_if<Scope::Variable>(&identifier.second))
|
||||
if (m_variablesScheduledForDeletion.count(var))
|
||||
deleteVariable(*var);
|
||||
// Directly in a function body block, we can also delete the function arguments,
|
||||
// which live in the virtual function scope.
|
||||
// However, doing so after the return variables are already allocated, seems to have an adverse
|
||||
// effect, so we only do it before that.
|
||||
if (!returnVariablesAndFunctionExitAreSetup() && !m_scope->functionScope && m_scope->superScope && m_scope->superScope->functionScope)
|
||||
for (auto const& identifier: m_scope->superScope->identifiers)
|
||||
if (Scope::Variable const* var = get_if<Scope::Variable>(&identifier.second))
|
||||
if (m_variablesScheduledForDeletion.count(var))
|
||||
deleteVariable(*var);
|
||||
|
||||
if (_popUnusedSlotsAtStackTop)
|
||||
while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1))
|
||||
@ -395,11 +409,11 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
|
||||
size_t height = 1;
|
||||
yulAssert(m_info.scopes.at(&_function.body), "");
|
||||
Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
|
||||
yulAssert(varScope, "");
|
||||
Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
|
||||
yulAssert(virtualFunctionScope, "");
|
||||
for (auto const& v: _function.parameters | ranges::views::reverse)
|
||||
{
|
||||
auto& var = std::get<Scope::Variable>(varScope->identifiers.at(v.name));
|
||||
auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
|
||||
m_context->variableStackHeights[&var] = height++;
|
||||
}
|
||||
|
||||
@ -410,17 +424,6 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
|
||||
m_assembly.setStackHeight(static_cast<int>(height));
|
||||
|
||||
for (auto const& v: _function.returnVariables)
|
||||
{
|
||||
auto& var = std::get<Scope::Variable>(varScope->identifiers.at(v.name));
|
||||
m_context->variableStackHeights[&var] = height++;
|
||||
// Preset stack slots for return variables to zero.
|
||||
m_assembly.appendConstant(u256(0));
|
||||
}
|
||||
|
||||
m_context->functionExitPoints.push(
|
||||
CodeTransformContext::JumpInfo{m_assembly.newLabelId(), m_assembly.stackHeight()}
|
||||
);
|
||||
CodeTransform subTransform(
|
||||
m_assembly,
|
||||
m_info,
|
||||
@ -430,8 +433,24 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
m_builtinContext,
|
||||
m_identifierAccess,
|
||||
m_useNamedLabelsForFunctions,
|
||||
m_context
|
||||
m_context,
|
||||
_function.returnVariables,
|
||||
m_assembly.newLabelId()
|
||||
);
|
||||
subTransform.m_scope = virtualFunctionScope;
|
||||
|
||||
if (m_allowStackOpt)
|
||||
// Immediately delete entirely unused parameters.
|
||||
for (auto const& v: _function.parameters | ranges::views::reverse)
|
||||
{
|
||||
auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name));
|
||||
if (util::valueOrDefault(m_context->variableReferences, &var, 0u) == 0)
|
||||
subTransform.deleteVariable(var);
|
||||
}
|
||||
|
||||
if (!m_allowStackOpt || _function.returnVariables.empty())
|
||||
subTransform.setupReturnVariablesAndFunctionExit();
|
||||
|
||||
subTransform(_function.body);
|
||||
if (!subTransform.m_stackErrors.empty())
|
||||
{
|
||||
@ -444,8 +463,17 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
}
|
||||
}
|
||||
|
||||
m_assembly.appendLabel(m_context->functionExitPoints.top().label);
|
||||
m_context->functionExitPoints.pop();
|
||||
if (!subTransform.returnVariablesAndFunctionExitAreSetup())
|
||||
subTransform.setupReturnVariablesAndFunctionExit();
|
||||
appendPopUntil(*subTransform.m_functionExitStackHeight);
|
||||
|
||||
yulAssert(
|
||||
subTransform.m_functionExitStackHeight &&
|
||||
*subTransform.m_functionExitStackHeight == m_assembly.stackHeight(),
|
||||
""
|
||||
);
|
||||
|
||||
m_assembly.appendLabel(*subTransform.m_functionExitLabel);
|
||||
|
||||
{
|
||||
// The stack layout here is:
|
||||
@ -456,12 +484,12 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
|
||||
// This vector holds the desired target positions of all stack slots and is
|
||||
// modified parallel to the actual stack.
|
||||
vector<int> stackLayout;
|
||||
stackLayout.push_back(static_cast<int>(_function.returnVariables.size())); // Move return label to the top
|
||||
stackLayout += vector<int>(_function.parameters.size(), -1); // discard all arguments
|
||||
|
||||
for (size_t i = 0; i < _function.returnVariables.size(); ++i)
|
||||
stackLayout.push_back(static_cast<int>(i)); // Move return values down, but keep order.
|
||||
vector<int> stackLayout(static_cast<size_t>(m_assembly.stackHeight()), -1);
|
||||
stackLayout[0] = static_cast<int>(_function.returnVariables.size()); // Move return label to the top
|
||||
for (auto&& [n, returnVariable]: ranges::views::enumerate(_function.returnVariables))
|
||||
stackLayout.at(m_context->variableStackHeights.at(
|
||||
&std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name))
|
||||
)) = static_cast<int>(n);
|
||||
|
||||
if (stackLayout.size() > 17)
|
||||
{
|
||||
@ -568,11 +596,10 @@ void CodeTransform::operator()(Continue const& _continue)
|
||||
|
||||
void CodeTransform::operator()(Leave const& _leaveStatement)
|
||||
{
|
||||
yulAssert(!m_context->functionExitPoints.empty(), "Invalid leave-statement. Requires surrounding function in code generation.");
|
||||
yulAssert(m_functionExitLabel, "Invalid leave-statement. Requires surrounding function in code generation.");
|
||||
yulAssert(m_functionExitStackHeight, "");
|
||||
m_assembly.setSourceLocation(_leaveStatement.location);
|
||||
|
||||
Context::JumpInfo const& jump = m_context->functionExitPoints.top();
|
||||
m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight));
|
||||
m_assembly.appendJumpTo(*m_functionExitLabel, appendPopUntil(*m_functionExitStackHeight));
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(Block const& _block)
|
||||
@ -583,7 +610,9 @@ void CodeTransform::operator()(Block const& _block)
|
||||
int blockStartStackHeight = m_assembly.stackHeight();
|
||||
visitStatements(_block.statements);
|
||||
|
||||
finalizeBlock(_block, blockStartStackHeight);
|
||||
bool isOutermostFunctionBodyBlock = m_scope && m_scope->superScope && m_scope->superScope->functionScope;
|
||||
bool performValidation = !m_allowStackOpt || !isOutermostFunctionBodyBlock;
|
||||
finalizeBlock(_block, performValidation ? make_optional(blockStartStackHeight) : nullopt);
|
||||
m_scope = originalScope;
|
||||
}
|
||||
|
||||
@ -607,6 +636,62 @@ void CodeTransform::visitExpression(Expression const& _expression)
|
||||
expectDeposit(1, height);
|
||||
}
|
||||
|
||||
void CodeTransform::setupReturnVariablesAndFunctionExit()
|
||||
{
|
||||
yulAssert(!returnVariablesAndFunctionExitAreSetup(), "");
|
||||
yulAssert(m_scope, "");
|
||||
|
||||
ScopeGuard scopeGuard([oldScope = m_scope, this] { m_scope = oldScope; });
|
||||
if (!m_scope->functionScope)
|
||||
{
|
||||
yulAssert(m_scope->superScope && m_scope->superScope->functionScope, "");
|
||||
m_scope = m_scope->superScope;
|
||||
}
|
||||
|
||||
// We could reuse unused slots for return variables, but it turns out this is detrimental in practice.
|
||||
m_unusedStackSlots.clear();
|
||||
|
||||
if (m_delayedReturnVariables.empty())
|
||||
{
|
||||
m_functionExitStackHeight = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate slots for return variables as if they were declared as variables in the virtual function scope.
|
||||
for (TypedName const& var: m_delayedReturnVariables)
|
||||
(*this)(VariableDeclaration{var.location, {var}, {}});
|
||||
|
||||
m_functionExitStackHeight = ranges::max(m_delayedReturnVariables | ranges::views::transform([&](TypedName const& _name) {
|
||||
return variableStackHeight(_name.name);
|
||||
})) + 1;
|
||||
m_delayedReturnVariables.clear();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool statementNeedsReturnVariableSetup(Statement const& _statement, vector<TypedName> const& _returnVariables)
|
||||
{
|
||||
if (holds_alternative<FunctionDefinition>(_statement))
|
||||
return true;
|
||||
if (
|
||||
holds_alternative<ExpressionStatement>(_statement) ||
|
||||
holds_alternative<Assignment>(_statement)
|
||||
)
|
||||
{
|
||||
ReferencesCounter referencesCounter{ReferencesCounter::CountWhat::OnlyVariables};
|
||||
referencesCounter.visit(_statement);
|
||||
auto isReferenced = [&referencesCounter](TypedName const& _returnVariable) {
|
||||
return referencesCounter.references().count(_returnVariable.name);
|
||||
};
|
||||
if (ranges::none_of(_returnVariables, isReferenced))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CodeTransform::visitStatements(vector<Statement> const& _statements)
|
||||
{
|
||||
std::optional<AbstractAssembly::LabelID> jumpTarget = std::nullopt;
|
||||
@ -614,6 +699,12 @@ void CodeTransform::visitStatements(vector<Statement> const& _statements)
|
||||
for (auto const& statement: _statements)
|
||||
{
|
||||
freeUnusedVariables();
|
||||
if (
|
||||
!m_delayedReturnVariables.empty() &&
|
||||
statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables)
|
||||
)
|
||||
setupReturnVariablesAndFunctionExit();
|
||||
|
||||
auto const* functionDefinition = std::get_if<FunctionDefinition>(&statement);
|
||||
if (functionDefinition && !jumpTarget)
|
||||
{
|
||||
@ -636,7 +727,7 @@ void CodeTransform::visitStatements(vector<Statement> const& _statements)
|
||||
freeUnusedVariables();
|
||||
}
|
||||
|
||||
void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight)
|
||||
void CodeTransform::finalizeBlock(Block const& _block, optional<int> blockStartStackHeight)
|
||||
{
|
||||
m_assembly.setSourceLocation(_block.location);
|
||||
|
||||
@ -657,8 +748,11 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight
|
||||
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
||||
}
|
||||
|
||||
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
|
||||
yulAssert(deposit == 0, "Invalid stack height at end of block: " + to_string(deposit));
|
||||
if (blockStartStackHeight)
|
||||
{
|
||||
int deposit = m_assembly.stackHeight() - *blockStartStackHeight;
|
||||
yulAssert(deposit == 0, "Invalid stack height at end of block: " + to_string(deposit));
|
||||
}
|
||||
}
|
||||
|
||||
void CodeTransform::generateMultiAssignment(vector<Identifier> const& _variableNames)
|
||||
@ -712,6 +806,13 @@ size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString
|
||||
return heightDiff;
|
||||
}
|
||||
|
||||
int CodeTransform::variableStackHeight(YulString _name) const
|
||||
{
|
||||
Scope::Variable const* var = get_if<Scope::Variable>(m_scope->lookup(_name));
|
||||
yulAssert(var, "");
|
||||
return static_cast<int>(m_context->variableStackHeights.at(var));
|
||||
}
|
||||
|
||||
void CodeTransform::expectDeposit(int _deposit, int _oldHeight) const
|
||||
{
|
||||
yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/ASTForward.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Scope.h>
|
||||
|
||||
#include <optional>
|
||||
@ -77,7 +77,6 @@ struct CodeTransformContext
|
||||
};
|
||||
|
||||
std::stack<ForLoopLabels> forLoopStack;
|
||||
std::stack<JumpInfo> functionExitPoints;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -139,7 +138,9 @@ public:
|
||||
_builtinContext,
|
||||
_identifierAccess,
|
||||
_useNamedLabelsForFunctions,
|
||||
nullptr
|
||||
nullptr,
|
||||
{},
|
||||
std::nullopt
|
||||
)
|
||||
{
|
||||
}
|
||||
@ -158,7 +159,9 @@ protected:
|
||||
BuiltinContext& _builtinContext,
|
||||
ExternalIdentifierAccess _identifierAccess,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
std::shared_ptr<Context> _context
|
||||
std::shared_ptr<Context> _context,
|
||||
std::vector<TypedName> _delayedReturnVariables,
|
||||
std::optional<AbstractAssembly::LabelID> _functionExitLabel
|
||||
);
|
||||
|
||||
void decreaseReference(YulString _name, Scope::Variable const& _var);
|
||||
@ -197,7 +200,7 @@ private:
|
||||
|
||||
/// Pops all variables declared in the block and checks that the stack height is equal
|
||||
/// to @a _blockStartStackHeight.
|
||||
void finalizeBlock(Block const& _block, int _blockStartStackHeight);
|
||||
void finalizeBlock(Block const& _block, std::optional<int> _blockStartStackHeight);
|
||||
|
||||
void generateMultiAssignment(std::vector<Identifier> const& _variableNames);
|
||||
void generateAssignment(Identifier const& _variableName);
|
||||
@ -209,6 +212,9 @@ private:
|
||||
/// opcode, otherwise checks for validity for a dup opcode.
|
||||
size_t variableHeightDiff(Scope::Variable const& _var, YulString _name, bool _forSwap);
|
||||
|
||||
/// Determines the stack height of the given variable. Throws if the variable is not in scope.
|
||||
int variableStackHeight(YulString _name) const;
|
||||
|
||||
void expectDeposit(int _deposit, int _oldHeight) const;
|
||||
|
||||
/// Stores the stack error in the list of errors, appends an invalid opcode
|
||||
@ -219,6 +225,13 @@ private:
|
||||
/// Returns the number of POP statements that have been appended.
|
||||
int appendPopUntil(int _targetDepth);
|
||||
|
||||
/// Allocates stack slots for remaining delayed return values and sets the function exit stack height.
|
||||
void setupReturnVariablesAndFunctionExit();
|
||||
bool returnVariablesAndFunctionExitAreSetup() const
|
||||
{
|
||||
return m_functionExitStackHeight.has_value();
|
||||
}
|
||||
|
||||
AbstractAssembly& m_assembly;
|
||||
AsmAnalysisInfo& m_info;
|
||||
Scope* m_scope = nullptr;
|
||||
@ -235,6 +248,16 @@ private:
|
||||
std::set<Scope::Variable const*> m_variablesScheduledForDeletion;
|
||||
std::set<int> m_unusedStackSlots;
|
||||
|
||||
/// A list of return variables for which no stack slots have been assigned yet.
|
||||
std::vector<TypedName> m_delayedReturnVariables;
|
||||
|
||||
/// Function exit label. Used as jump target for ``leave``.
|
||||
std::optional<AbstractAssembly::LabelID> m_functionExitLabel;
|
||||
/// The required stack height at the function exit label.
|
||||
/// This is the minimal stack height covering all return variables. Only set after all
|
||||
/// return variables were assigned slots.
|
||||
std::optional<int> m_functionExitStackHeight;
|
||||
|
||||
std::vector<StackTooDeepError> m_stackErrors;
|
||||
};
|
||||
|
||||
|
@ -150,6 +150,7 @@ sub_0: assembly {
|
||||
revert(0x00, 0x24)
|
||||
/* "#utility.yul":196:425 */
|
||||
tag_22:
|
||||
/* "#utility.yul":236:239 */
|
||||
0x00
|
||||
/* "#utility.yul":267:268 */
|
||||
dup3
|
||||
|
@ -155,6 +155,7 @@ sub_0: assembly {
|
||||
revert(0x00, 0x24)
|
||||
/* "#utility.yul":196:425 */
|
||||
tag_19:
|
||||
/* "#utility.yul":236:239 */
|
||||
0x00
|
||||
/* "#utility.yul":267:268 */
|
||||
dup3
|
||||
|
@ -62,7 +62,7 @@ contract C {
|
||||
// test_bytes() ->
|
||||
// gas irOptimized: 511133
|
||||
// gas legacy: 466763
|
||||
// gas legacyOptimized: 374537
|
||||
// gas legacyOptimized: 374591
|
||||
// test_uint256() ->
|
||||
// gas irOptimized: 706775
|
||||
// gas legacy: 634592
|
||||
|
@ -63,7 +63,7 @@ contract C {
|
||||
// test_bytes() ->
|
||||
// gas irOptimized: 511133
|
||||
// gas legacy: 466763
|
||||
// gas legacyOptimized: 374537
|
||||
// gas legacyOptimized: 374591
|
||||
// test_uint256() ->
|
||||
// gas irOptimized: 706775
|
||||
// gas legacy: 634592
|
||||
|
@ -117,16 +117,63 @@ BOOST_AUTO_TEST_CASE(many_variables_many_uses)
|
||||
BOOST_CHECK_EQUAL(out, "f: 10 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(many_return_variables)
|
||||
BOOST_AUTO_TEST_CASE(many_return_variables_unused_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 {
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "f: 3 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(many_return_variables_used_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 {
|
||||
r1 := 0
|
||||
sstore(a, b)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "f: 5 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_functions)
|
||||
BOOST_AUTO_TEST_CASE(multiple_functions_used_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 {
|
||||
r1 := 0
|
||||
sstore(a, b)
|
||||
}
|
||||
function g(r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19) -> x, y {
|
||||
x := 0
|
||||
sstore(r1, r2)
|
||||
}
|
||||
function h(x) {
|
||||
let r1 := 0
|
||||
let r2 := 0
|
||||
let r3 := 0
|
||||
let r4 := 0
|
||||
let r5 := 0
|
||||
let r6 := 0
|
||||
let r7 := 0
|
||||
let r8 := 0
|
||||
let r9 := 0
|
||||
let r10 := 0
|
||||
let r11 := 0
|
||||
let r12 := 0
|
||||
let r13 := 0
|
||||
let r14 := 0
|
||||
let r15 := 0
|
||||
let r16 := 0
|
||||
let r17 := 0
|
||||
let r18 := 0
|
||||
x := add(add(add(add(add(add(add(add(add(add(add(add(x, r12), r11), r10), r9), r8), r7), r6), r5), r4), r3), r2), r1)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "h: 9 g: 5 f: 5 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_functions_unused_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 {
|
||||
@ -155,10 +202,47 @@ BOOST_AUTO_TEST_CASE(multiple_functions)
|
||||
x := add(add(add(add(add(add(add(add(add(add(add(add(x, r12), r11), r10), r9), r8), r7), r6), r5), r4), r3), r2), r1)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "h: 9 f: 3 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(nested_used_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function h(x) {
|
||||
let r1 := 0
|
||||
let r2 := 0
|
||||
let r3 := 0
|
||||
let r4 := 0
|
||||
let r5 := 0
|
||||
let r6 := 0
|
||||
let r7 := 0
|
||||
let r8 := 0
|
||||
let r9 := 0
|
||||
let r10 := 0
|
||||
let r11 := 0
|
||||
let r12 := 0
|
||||
let r13 := 0
|
||||
let r14 := 0
|
||||
let r15 := 0
|
||||
let r16 := 0
|
||||
let r17 := 0
|
||||
let r18 := 0
|
||||
function f(a, b) -> t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19 {
|
||||
function g(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19) -> w, v {
|
||||
w := v
|
||||
sstore(s1, s2)
|
||||
}
|
||||
t1 := t2
|
||||
sstore(a, b)
|
||||
}
|
||||
x := add(add(add(add(add(add(add(add(add(add(add(add(x, r12), r11), r10), r9), r8), r7), r6), r5), r4), r3), r2), r1)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "h: 9 g: 5 f: 5 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(nested)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(nested_unused_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
function h(x) {
|
||||
@ -187,10 +271,42 @@ BOOST_AUTO_TEST_CASE(nested)
|
||||
x := add(add(add(add(add(add(add(add(add(add(add(add(x, r12), r11), r10), r9), r8), r7), r6), r5), r4), r3), r2), r1)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "h: 9 g: 5 f: 5 ");
|
||||
BOOST_CHECK_EQUAL(out, "h: 9 f: 3 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(also_in_outer_block)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(also_in_outer_block_used_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
let x := 0
|
||||
let r1 := 0
|
||||
let r2 := 0
|
||||
let r3 := 0
|
||||
let r4 := 0
|
||||
let r5 := 0
|
||||
let r6 := 0
|
||||
let r7 := 0
|
||||
let r8 := 0
|
||||
let r9 := 0
|
||||
let r10 := 0
|
||||
let r11 := 0
|
||||
let r12 := 0
|
||||
let r13 := 0
|
||||
let r14 := 0
|
||||
let r15 := 0
|
||||
let r16 := 0
|
||||
let r17 := 0
|
||||
let r18 := 0
|
||||
x := add(add(add(add(add(add(add(add(add(add(add(add(x, r12), r11), r10), r9), r8), r7), r6), r5), r4), r3), r2), r1)
|
||||
function g(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19) -> w, v {
|
||||
w := v
|
||||
sstore(s1, s2)
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "g: 5 : 9 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(also_in_outer_block_unused_arguments)
|
||||
{
|
||||
string out = check(R"({
|
||||
let x := 0
|
||||
@ -216,7 +332,7 @@ BOOST_AUTO_TEST_CASE(also_in_outer_block)
|
||||
function g(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19) -> w, v {
|
||||
}
|
||||
})");
|
||||
BOOST_CHECK_EQUAL(out, "g: 5 : 9 ");
|
||||
BOOST_CHECK_EQUAL(out, ": 9 ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
function f(a, b, c) -> x { pop(address()) sstore(a, c) pop(callvalue()) x := b }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x17
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// DUP3
|
||||
// DUP2
|
||||
// SSTORE
|
||||
// POP
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// DUP2
|
||||
// SWAP1
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP3
|
||||
// SWAP2
|
||||
// POP
|
||||
// POP
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -12,18 +12,17 @@
|
||||
// PUSH1 0xD
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x16
|
||||
// PUSH1 0x15
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// POP
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// JUMPDEST
|
||||
// SWAP3
|
||||
// SWAP2
|
||||
// POP
|
||||
// POP
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x20
|
||||
// PUSH1 0x1F
|
||||
// PUSH1 0x4
|
||||
// PUSH1 0x3
|
||||
// PUSH1 0xD
|
||||
|
@ -0,0 +1,116 @@
|
||||
{
|
||||
function f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) -> x {
|
||||
mstore(0x0100, a1)
|
||||
mstore(0x0120, a2)
|
||||
mstore(0x0140, a3)
|
||||
mstore(0x0160, a4)
|
||||
mstore(0x0180, a5)
|
||||
mstore(0x01A0, a6)
|
||||
mstore(0x01C0, a7)
|
||||
mstore(0x01E0, a8)
|
||||
mstore(0x0200, a9)
|
||||
mstore(0x0220, a10)
|
||||
mstore(0x0240, a11)
|
||||
mstore(0x0260, a12)
|
||||
mstore(0x0280, a13)
|
||||
mstore(0x02A0, a14)
|
||||
mstore(0x02C0, a15)
|
||||
mstore(0x02E0, a16)
|
||||
mstore(0x0300, a17)
|
||||
mstore(0x0320, a18)
|
||||
mstore(0x0340, a19)
|
||||
x := a20
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x80
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// DUP1
|
||||
// PUSH2 0x100
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x120
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x140
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x160
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x180
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x1A0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x1C0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x1E0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x200
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x220
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x240
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x260
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x280
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x2A0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x2C0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x2E0
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x300
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x320
|
||||
// MSTORE
|
||||
// POP
|
||||
// DUP1
|
||||
// PUSH2 0x340
|
||||
// MSTORE
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// DUP2
|
||||
// SWAP1
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP2
|
||||
// SWAP1
|
||||
// POP
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -7,8 +7,8 @@
|
||||
// PUSH1 0x8
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// POP
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// POP
|
||||
// POP
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
|
@ -8,18 +8,17 @@
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x11
|
||||
// PUSH1 0x10
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x0
|
||||
// JUMPDEST
|
||||
// SWAP5
|
||||
// POP
|
||||
// SWAP5
|
||||
// SWAP3
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// SWAP1
|
||||
// SWAP2
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
|
@ -4,14 +4,18 @@
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x1F
|
||||
// PUSH1 0x1E
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x3
|
||||
// SWAP4
|
||||
// POP
|
||||
// PUSH1 0x3
|
||||
// SWAP1
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x9
|
||||
// PUSH1 0x2
|
||||
// SWAP2
|
||||
@ -21,12 +25,7 @@
|
||||
// MSTORE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP5
|
||||
// POP
|
||||
// SWAP5
|
||||
// SWAP3
|
||||
// POP
|
||||
// POP
|
||||
// POP
|
||||
// SWAP1
|
||||
// SWAP2
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
function f() -> x { pop(address()) { pop(callvalue()) } }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0xD
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
function f() -> x { pop(address()) let y := callvalue() }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0xD
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
function f() -> x { pop(address()) for { pop(callvalue()) } 0 {} { } }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x19
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x0
|
||||
// ISZERO
|
||||
// PUSH1 0x15
|
||||
// JUMPI
|
||||
// JUMPDEST
|
||||
// PUSH1 0xA
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
function f() -> x { pop(address()) if 1 { pop(callvalue()) } }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x14
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x1
|
||||
// ISZERO
|
||||
// PUSH1 0x10
|
||||
// JUMPI
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
function f() -> x { pop(address()) leave pop(callvalue()) }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x10
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0xD
|
||||
// JUMP
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
function f() -> x { pop(address()) sstore(0, x) pop(callvalue()) }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x11
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// ADDRESS
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// DUP1
|
||||
// PUSH1 0x0
|
||||
// SSTORE
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
function f() -> x { pop(callvalue()) }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0xB
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
function f() -> x, y, z { pop(callvalue()) }
|
||||
}
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x11
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// CALLVALUE
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x0
|
||||
// PUSH1 0x0
|
||||
// JUMPDEST
|
||||
// SWAP1
|
||||
// SWAP2
|
||||
// SWAP3
|
||||
// JUMP
|
||||
// JUMPDEST
|
@ -10,15 +10,14 @@
|
||||
// ====
|
||||
// stackOptimization: true
|
||||
// ----
|
||||
// PUSH1 0x15
|
||||
// PUSH1 0x14
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// POP
|
||||
// POP
|
||||
// PUSH1 0x0
|
||||
// JUMPDEST
|
||||
// SWAP3
|
||||
// SWAP2
|
||||
// POP
|
||||
// POP
|
||||
// SWAP1
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x0
|
||||
@ -28,13 +27,13 @@
|
||||
// SWAP2
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x1F
|
||||
// PUSH1 0x1E
|
||||
// PUSH1 0x2
|
||||
// PUSH1 0x1
|
||||
// PUSH1 0x3
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x29
|
||||
// PUSH1 0x28
|
||||
// PUSH1 0x4
|
||||
// PUSH1 0x3
|
||||
// PUSH1 0x3
|
||||
@ -43,12 +42,12 @@
|
||||
// SWAP1
|
||||
// POP
|
||||
// POP
|
||||
// PUSH1 0x32
|
||||
// PUSH1 0xC
|
||||
// PUSH1 0x31
|
||||
// PUSH1 0xB
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// PUSH1 0x38
|
||||
// PUSH1 0xC
|
||||
// PUSH1 0x37
|
||||
// PUSH1 0xB
|
||||
// JUMP
|
||||
// JUMPDEST
|
||||
// SWAP2
|
||||
|
@ -31,6 +31,7 @@ object "Contract" {
|
||||
// tag_7
|
||||
// jumpi
|
||||
// /* "source":82:87 */
|
||||
// pop
|
||||
// jump(tag_6)
|
||||
// /* "source":75:77 */
|
||||
// tag_7:
|
||||
@ -47,8 +48,8 @@ object "Contract" {
|
||||
// jump // in
|
||||
// tag_8:
|
||||
// /* "source":73:104 */
|
||||
// tag_6:
|
||||
// pop
|
||||
// tag_6:
|
||||
// jump // out
|
||||
// tag_1:
|
||||
// /* "source":109:113 */
|
||||
@ -59,6 +60,6 @@ object "Contract" {
|
||||
// tag_5
|
||||
// jump // in
|
||||
// tag_9:
|
||||
// Bytecode: 6025565b600b6001600e565b5b565b80156017576022565b602160028201600e565b5b50565b602d6001600e565b
|
||||
// Opcodes: PUSH1 0x25 JUMP JUMPDEST PUSH1 0xB PUSH1 0x1 PUSH1 0xE JUMP JUMPDEST JUMPDEST JUMP JUMPDEST DUP1 ISZERO PUSH1 0x17 JUMPI PUSH1 0x22 JUMP JUMPDEST PUSH1 0x21 PUSH1 0x2 DUP3 ADD PUSH1 0xE JUMP JUMPDEST JUMPDEST POP JUMP JUMPDEST PUSH1 0x2D PUSH1 0x1 PUSH1 0xE JUMP JUMPDEST
|
||||
// SourceMappings: 33:21:0:-:0;;;48:4;50:1;48:4;:::i;:::-;46:8;:::o;59:45::-;78:1;75:2;;;82:5;;75:2;90:12;99:1;96;92:9;90:12;:::i;:::-;73:31;;:::o;:::-;109:4;111:1;109:4;:::i;:::-
|
||||
// Bytecode: 6026565b600b6001600e565b5b565b8015601857506024565b602260028201600e565b505b565b602e6001600e565b
|
||||
// Opcodes: PUSH1 0x26 JUMP JUMPDEST PUSH1 0xB PUSH1 0x1 PUSH1 0xE JUMP JUMPDEST JUMPDEST JUMP JUMPDEST DUP1 ISZERO PUSH1 0x18 JUMPI POP PUSH1 0x24 JUMP JUMPDEST PUSH1 0x22 PUSH1 0x2 DUP3 ADD PUSH1 0xE JUMP JUMPDEST POP JUMPDEST JUMP JUMPDEST PUSH1 0x2E PUSH1 0x1 PUSH1 0xE JUMP JUMPDEST
|
||||
// SourceMappings: 33:21:0:-:0;;;48:4;50:1;48:4;:::i;:::-;46:8;:::o;59:45::-;78:1;75:2;;;82:5;;;75:2;90:12;99:1;96;92:9;90:12;:::i;:::-;73:31;;:::o;:::-;109:4;111:1;109:4;:::i;:::-
|
||||
|
Loading…
Reference in New Issue
Block a user