Merge pull request #10015 from ethereum/moveFunctionArgumentsToMemoryNew

Stack Limit Evader: Move function arguments and return values to memory (v2)
This commit is contained in:
Harikrishnan Mulackal 2021-06-28 18:49:40 +02:00 committed by GitHub
commit d91dc9953f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1219 additions and 95 deletions

View File

@ -4,6 +4,9 @@ Language Features:
Compiler Features:
* Yul EVM Code Transform: Do not reuse stack slots that immediately become unreachable.
* Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables).
* Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default).
Bugfixes:

View File

@ -129,6 +129,8 @@ add_library(yul
optimiser/FullInliner.h
optimiser/FunctionCallFinder.cpp
optimiser/FunctionCallFinder.h
optimiser/FunctionDefinitionCollector.cpp
optimiser/FunctionDefinitionCollector.h
optimiser/FunctionGrouper.cpp
optimiser/FunctionGrouper.h
optimiser/FunctionHoister.cpp

View File

@ -182,16 +182,24 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
else
m_variablesScheduledForDeletion.insert(&var);
}
else if (m_unusedStackSlots.empty())
atTopOfStack = false;
else
{
auto slot = static_cast<size_t>(*m_unusedStackSlots.begin());
m_unusedStackSlots.erase(m_unusedStackSlots.begin());
m_context->variableStackHeights[&var] = slot;
if (size_t heightDiff = variableHeightDiff(var, varName, true))
m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
m_assembly.appendInstruction(evmasm::Instruction::POP);
bool foundUnusedSlot = false;
for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it)
{
if (m_assembly.stackHeight() - *it > 17)
continue;
foundUnusedSlot = true;
auto slot = static_cast<size_t>(*it);
m_unusedStackSlots.erase(it);
m_context->variableStackHeights[&var] = slot;
if (size_t heightDiff = variableHeightDiff(var, varName, true))
m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1)));
m_assembly.appendInstruction(evmasm::Instruction::POP);
break;
}
if (!foundUnusedSlot)
atTopOfStack = false;
}
}
}
@ -404,7 +412,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
subTransform.deleteVariable(var);
}
if (!m_allowStackOpt || _function.returnVariables.empty())
if (!m_allowStackOpt)
subTransform.setupReturnVariablesAndFunctionExit();
subTransform(_function.body);
@ -594,6 +602,7 @@ void CodeTransform::visitExpression(Expression const& _expression)
void CodeTransform::setupReturnVariablesAndFunctionExit()
{
yulAssert(isInsideFunction(), "");
yulAssert(!returnVariablesAndFunctionExitAreSetup(), "");
yulAssert(m_scope, "");
@ -656,7 +665,8 @@ void CodeTransform::visitStatements(vector<Statement> const& _statements)
{
freeUnusedVariables();
if (
!m_delayedReturnVariables.empty() &&
isInsideFunction() &&
!returnVariablesAndFunctionExitAreSetup() &&
statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables)
)
setupReturnVariablesAndFunctionExit();

View File

@ -181,6 +181,10 @@ private:
{
return m_functionExitStackHeight.has_value();
}
bool isInsideFunction() const
{
return m_functionExitLabel.has_value();
}
AbstractAssembly& m_assembly;
AsmAnalysisInfo& m_info;

View File

@ -0,0 +1,36 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libyul/optimiser/FunctionDefinitionCollector.h>
#include <libyul/AST.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block)
{
FunctionDefinitionCollector functionDefinitionCollector;
functionDefinitionCollector(_block);
return functionDefinitionCollector.m_functionDefinitions;
}
void FunctionDefinitionCollector::operator()(FunctionDefinition const& _functionDefinition)
{
m_functionDefinitions[_functionDefinition.name] = &_functionDefinition;
ASTWalker::operator()(_functionDefinition);
}

View File

@ -0,0 +1,44 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* AST walker that finds all function definitions and stores them into a map indexed by the function names.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <map>
namespace solidity::yul
{
/**
* AST walker that finds all function definitions and stores them into a map indexed by the function names.
*
* Prerequisite: Disambiguator
*/
class FunctionDefinitionCollector: ASTWalker
{
public:
static std::map<YulString, FunctionDefinition const*> run(Block& _block);
private:
using ASTWalker::operator();
void operator()(FunctionDefinition const& _functionDefinition) override;
std::map<YulString, FunctionDefinition const*> m_functionDefinitions;
};
}

View File

@ -18,6 +18,7 @@
#include <libyul/optimiser/StackLimitEvader.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/FunctionCallFinder.h>
#include <libyul/optimiser/FunctionDefinitionCollector.h>
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/optimiser/StackToMemoryMover.h>
#include <libyul/backends/evm/EVMDialect.h>
@ -28,6 +29,9 @@
#include <libsolutil/Algorithms.h>
#include <libsolutil/CommonData.h>
#include <range/v3/view/concat.hpp>
#include <range/v3/view/take.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
@ -62,25 +66,42 @@ struct MemoryOffsetAllocator
for (YulString child: callGraph.at(_function))
requiredSlots = std::max(run(child), requiredSlots);
if (unreachableVariables.count(_function))
if (auto const* unreachables = util::valueOrNullptr(unreachableVariables, _function))
{
yulAssert(!slotAllocations.count(_function), "");
for (YulString variable: unreachableVariables.at(_function))
if (variable.empty())
{
// TODO: Too many function arguments or return parameters.
}
else
if (FunctionDefinition const* functionDefinition = util::valueOrDefault(functionDefinitions, _function, nullptr, util::allow_copy))
if (
size_t totalArgCount = functionDefinition->returnVariables.size() + functionDefinition->parameters.size();
totalArgCount > 16
)
for (TypedName const& var: ranges::concat_view(
functionDefinition->parameters,
functionDefinition->returnVariables
) | ranges::views::take(totalArgCount - 16))
slotAllocations[var.name] = requiredSlots++;
// Assign slots for all variables that become unreachable in the function body, if the above did not
// assign a slot for them already.
for (YulString variable: *unreachables)
// The empty case is a function with too many arguments or return values,
// which was already handled above.
if (!variable.empty() && !slotAllocations.count(variable))
slotAllocations[variable] = requiredSlots++;
}
return slotsRequiredForFunction[_function] = requiredSlots;
}
/// Maps function names to the set of unreachable variables in that function.
/// An empty variable name means that the function has too many arguments or return variables.
map<YulString, set<YulString>> const& unreachableVariables;
/// The graph of immediate function calls of all functions.
map<YulString, set<YulString>> const& callGraph;
/// Maps the name of each user-defined function to its definition.
map<YulString, FunctionDefinition const*> const& functionDefinitions;
/// Maps variable names to the memory slot the respective variable is assigned.
map<YulString, uint64_t> slotAllocations{};
/// Maps function names to the number of memory slots the respective function requires.
map<YulString, uint64_t> slotsRequiredForFunction{};
};
@ -116,6 +137,8 @@ void StackLimitEvader::run(
// Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort).
u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front());
yulAssert(reservedMemory < u256(1) << 32 - 1, "");
for (FunctionCall const* memoryGuardCall: memoryGuardCalls)
if (reservedMemory != literalArgumentValue(*memoryGuardCall))
return;
@ -127,12 +150,14 @@ void StackLimitEvader::run(
if (_unreachableVariables.count(function))
return;
MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls};
map<YulString, FunctionDefinition const*> functionDefinitions = FunctionDefinitionCollector::run(*_object.code);
MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls, functionDefinitions};
uint64_t requiredSlots = memoryOffsetAllocator.run();
yulAssert(requiredSlots < (uint64_t(1) << 32) - 1, "");
StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code);
yulAssert(requiredSlots < std::numeric_limits<uint64_t>::max() / 32, "");
reservedMemory += 32 * requiredSlots;
for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring))
{

View File

@ -15,13 +15,19 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libyul/optimiser/StackToMemoryMover.h>
#include <libyul/optimiser/FunctionDefinitionCollector.h>
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AST.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <range/v3/algorithm/none_of.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/zip.hpp>
#include <range/v3/range/conversion.hpp>
using namespace std;
using namespace solidity;
@ -44,7 +50,7 @@ vector<Statement> generateMemoryStore(
Identifier{_debugData, memoryStoreFunction->name},
{
Literal{_debugData, LiteralKind::Number, _mpos, {}},
std::move(_value)
move(_value)
}
}});
return result;
@ -77,17 +83,30 @@ void StackToMemoryMover::run(
)
{
VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots);
StackToMemoryMover stackToMemoryMover(_context, memoryOffsetTracker);
StackToMemoryMover stackToMemoryMover(
_context,
memoryOffsetTracker,
util::applyMap(
FunctionDefinitionCollector::run(_block),
util::mapTuple([](YulString _name, FunctionDefinition const* _funDef) {
return make_pair(_name, _funDef->returnVariables);
}),
map<YulString, TypedNameList>{}
)
);
stackToMemoryMover(_block);
_block.statements += move(stackToMemoryMover.m_newFunctionDefinitions);
}
StackToMemoryMover::StackToMemoryMover(
OptimiserStepContext& _context,
VariableMemoryOffsetTracker const& _memoryOffsetTracker
VariableMemoryOffsetTracker const& _memoryOffsetTracker,
map<YulString, TypedNameList> _functionReturnVariables
):
m_context(_context),
m_memoryOffsetTracker(_memoryOffsetTracker),
m_nameDispenser(_context.dispenser)
m_nameDispenser(_context.dispenser),
m_functionReturnVariables(move(_functionReturnVariables))
{
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert(
@ -98,89 +117,186 @@ m_nameDispenser(_context.dispenser)
void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)
{
for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables)
if (m_memoryOffsetTracker(param.name))
{
// TODO: we cannot handle function parameters yet.
return;
}
// It is important to first visit the function body, so that it doesn't replace the memory inits for
// variable arguments we might generate below.
ASTModifier::operator()(_functionDefinition);
vector<Statement> memoryVariableInits;
// All function parameters with a memory slot are moved at the beginning of the function body.
for (TypedName const& param: _functionDefinition.parameters)
if (auto slot = m_memoryOffsetTracker(param.name))
memoryVariableInits += generateMemoryStore(
m_context.dialect,
param.debugData,
*slot,
Identifier{param.debugData, param.name}
);
// All memory return variables have to be initialized to zero in memory.
for (TypedName const& returnVariable: _functionDefinition.returnVariables)
if (auto slot = m_memoryOffsetTracker(returnVariable.name))
memoryVariableInits += generateMemoryStore(
m_context.dialect,
returnVariable.debugData,
*slot,
Literal{returnVariable.debugData, LiteralKind::Number, "0"_yulstring, {}}
);
// Special case of a function with a single return argument that needs to move to memory.
if (_functionDefinition.returnVariables.size() == 1 && m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name))
{
TypedNameList stackParameters = _functionDefinition.parameters | ranges::views::filter(
not_fn(m_memoryOffsetTracker)
) | ranges::to<TypedNameList>;
// Generate new function without return variable and with only the non-moved parameters.
YulString newFunctionName = m_context.dispenser.newName(_functionDefinition.name);
m_newFunctionDefinitions.emplace_back(FunctionDefinition{
_functionDefinition.debugData,
newFunctionName,
stackParameters,
{},
move(_functionDefinition.body)
});
// Generate new names for the arguments to maintain disambiguation.
std::map<YulString, YulString> newArgumentNames;
for (TypedName const& _var: stackParameters)
newArgumentNames[_var.name] = m_context.dispenser.newName(_var.name);
for (auto& parameter: _functionDefinition.parameters)
parameter.name = util::valueOrDefault(newArgumentNames, parameter.name, parameter.name);
// Replace original function by a call to the new function and an assignment to the return variable from memory.
_functionDefinition.body = Block{_functionDefinition.debugData, move(memoryVariableInits)};
_functionDefinition.body.statements.emplace_back(ExpressionStatement{
_functionDefinition.debugData,
FunctionCall{
_functionDefinition.debugData,
Identifier{_functionDefinition.debugData, newFunctionName},
stackParameters | ranges::views::transform([&](TypedName const& _arg) {
return Expression{Identifier{_arg.debugData, newArgumentNames.at(_arg.name)}};
}) | ranges::to<vector<Expression>>
}
});
_functionDefinition.body.statements.emplace_back(Assignment{
_functionDefinition.debugData,
{Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}},
make_unique<Expression>(generateMemoryLoad(
m_context.dialect,
_functionDefinition.debugData,
*m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name)
))
});
return;
}
if (!memoryVariableInits.empty())
_functionDefinition.body.statements = move(memoryVariableInits) + move(_functionDefinition.body.statements);
_functionDefinition.returnVariables = _functionDefinition.returnVariables | ranges::views::filter(
not_fn(m_memoryOffsetTracker)
) | ranges::to<TypedNameList>;
}
void StackToMemoryMover::operator()(Block& _block)
{
using OptionalStatements = std::optional<vector<Statement>>;
auto rewriteAssignmentOrVariableDeclaration = [&](
using OptionalStatements = optional<vector<Statement>>;
auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this](
auto& _stmt,
auto const& _variables
auto& _lhsVars
) -> OptionalStatements {
using StatementType = decay_t<decltype(_stmt)>;
auto debugData = _stmt.debugData;
if (_lhsVars.size() == 1)
{
if (optional<YulString> offset = m_memoryOffsetTracker(_lhsVars.front().name))
return generateMemoryStore(
m_context.dialect,
debugData,
*offset,
_stmt.value ? *move(_stmt.value) : Literal{debugData, LiteralKind::Number, "0"_yulstring, {}}
);
else
return {};
}
vector<optional<YulString>> rhsMemorySlots;
if (_stmt.value)
visit(*_stmt.value);
bool leftHandSideNeedsMoving = util::contains_if(_variables, [&](auto const& var) {
return m_memoryOffsetTracker(var.name);
});
if (!leftHandSideNeedsMoving)
{
FunctionCall const* functionCall = get_if<FunctionCall>(_stmt.value.get());
yulAssert(functionCall, "");
if (m_context.dialect.builtin(functionCall->functionName.name))
rhsMemorySlots = vector<optional<YulString>>(_lhsVars.size(), nullopt);
else
rhsMemorySlots =
m_functionReturnVariables.at(functionCall->functionName.name) |
ranges::views::transform(m_memoryOffsetTracker) |
ranges::to<vector<optional<YulString>>>;
}
else
rhsMemorySlots = vector<optional<YulString>>(_lhsVars.size(), nullopt);
// Nothing to do, if the right-hand-side remains entirely on the stack and
// none of the variables in the left-hand-side are moved.
if (
ranges::none_of(rhsMemorySlots, [](optional<YulString> const& _slot) { return _slot.has_value(); }) &&
!util::contains_if(_lhsVars, m_memoryOffsetTracker)
)
return {};
if (_variables.size() == 1)
{
optional<YulString> offset = m_memoryOffsetTracker(_variables.front().name);
yulAssert(offset, "");
return generateMemoryStore(
m_context.dialect,
_stmt.debugData,
*offset,
_stmt.value ? *std::move(_stmt.value) : Literal{_stmt.debugData, LiteralKind::Number, "0"_yulstring, {}}
);
}
VariableDeclaration tempDecl{_stmt.debugData, {}, std::move(_stmt.value)};
vector<Statement> memoryAssignments;
vector<Statement> variableAssignments;
for (auto& var: _variables)
{
YulString tempVarName = m_nameDispenser.newName(var.name);
tempDecl.variables.emplace_back(TypedName{var.debugData, tempVarName, {}});
VariableDeclaration tempDecl{debugData, {}, move(_stmt.value)};
if (optional<YulString> offset = m_memoryOffsetTracker(var.name))
yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
{
unique_ptr<Expression> rhs;
if (rhsSlot)
rhs = make_unique<Expression>(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot));
else
{
YulString tempVarName = m_nameDispenser.newName(lhsVar.name);
tempDecl.variables.emplace_back(TypedName{lhsVar.debugData, tempVarName, {}});
rhs = make_unique<Expression>(Identifier{debugData, tempVarName});
}
if (optional<YulString> offset = m_memoryOffsetTracker(lhsVar.name))
memoryAssignments += generateMemoryStore(
m_context.dialect,
_stmt.debugData,
*offset,
Identifier{_stmt.debugData, tempVarName}
move(*rhs)
);
else
variableAssignments.emplace_back(StatementType{
_stmt.debugData,
{move(var)},
make_unique<Expression>(Identifier{_stmt.debugData, tempVarName})
debugData,
{ move(lhsVar) },
move(rhs)
});
}
std::vector<Statement> result;
result.emplace_back(std::move(tempDecl));
std::reverse(memoryAssignments.begin(), memoryAssignments.end());
result += std::move(memoryAssignments);
std::reverse(variableAssignments.begin(), variableAssignments.end());
result += std::move(variableAssignments);
vector<Statement> result;
if (tempDecl.variables.empty())
result.emplace_back(ExpressionStatement{debugData, *move(tempDecl.value)});
else
result.emplace_back(move(tempDecl));
reverse(memoryAssignments.begin(), memoryAssignments.end());
result += move(memoryAssignments);
reverse(variableAssignments.begin(), variableAssignments.end());
result += move(variableAssignments);
return OptionalStatements{move(result)};
};
util::iterateReplacing(
_block.statements,
[&](Statement& _statement)
[&](Statement& _statement) -> OptionalStatements
{
return std::visit(util::GenericVisitor{
[&](Assignment& _assignment) -> OptionalStatements
{
return rewriteAssignmentOrVariableDeclaration(_assignment, _assignment.variableNames);
},
[&](VariableDeclaration& _varDecl) -> OptionalStatements
{
return rewriteAssignmentOrVariableDeclaration(_varDecl, _varDecl.variables);
},
[&](auto& _stmt) -> OptionalStatements { (*this)(_stmt); return {}; }
}, _statement);
visit(_statement);
if (auto* assignment = get_if<Assignment>(&_statement))
return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames);
else if (auto* varDecl = get_if<VariableDeclaration>(&_statement))
return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables);
return {};
}
);
}
@ -188,7 +304,7 @@ void StackToMemoryMover::operator()(Block& _block)
void StackToMemoryMover::visit(Expression& _expression)
{
ASTModifier::visit(_expression);
if (Identifier* identifier = std::get_if<Identifier>(&_expression))
if (Identifier* identifier = get_if<Identifier>(&_expression))
if (optional<YulString> offset = m_memoryOffsetTracker(identifier->name))
_expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset);
}
@ -202,5 +318,15 @@ optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(
return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))};
}
else
return std::nullopt;
return nullopt;
}
optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(TypedName const& _variable) const
{
return (*this)(_variable.name);
}
optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const
{
return (*this)(_variable.name);
}

View File

@ -22,6 +22,9 @@
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/ASTForward.h>
#include <liblangutil/SourceLocation.h>
#include <libsolutil/Common.h>
namespace solidity::yul
@ -52,6 +55,16 @@ namespace solidity::yul
* let c := _3
* let a := _1
*
* In case f has return parameters that are moved to memory, fewer variables are returned and the return values read
* from memory instead. Assume the third return parameter of f (i.e. c) has to be moved to memory:
* let a, b, c, d := f()
* then it is replaced by
* let _1, _2, _4 := f()
* mstore(<memory offset for d>, _4)
* mstore(<memory offset for b>, _2)
* let c := mload(<memory offset of third return parameter of f>)
* let a := _1
*
* Assignments to single variables are replaced by mstore's:
* If a is in the map, replace
* a := expr
@ -70,8 +83,44 @@ namespace solidity::yul
*
* Replace all references to a variable ``a`` in the map by ``mload(<memory offset for a>)``.
*
* If a visited function has arguments or return parameters that are contained in the map,
* the entire function is skipped (no local variables in the function will be moved at all).
* Function arguments are moved at the beginning of a function body:
* If a1 is in the map, replace
* function f(a1, a2, ..., a17)
* {
* ...
* sstore(a1, a17)
* }
* by
* function f(a1, a2, ..., a17)
* {
* mstore(<memory offset for a1>, a1)
* ...
* sstore(mload(<memory offset for a1>, a17)
* }
* This relies on the code transform popping arguments that are no longer used, if they are on the stack top.
*
* Functions with only one return argument that has to be moved are encapsulated in a wrapper function as follows:
* Suppose b and r need to be moved in:
* function f(a, b) -> r
* {
* ...body of f...
* r := b
* ...body of f continued...
* }
* then replace by:
* function f(a, b) -> r
* {
* mstore(<memory offset of b>, b)
* mstore(<memory offset of r>, 0)
* f_1(a)
* r := mload(<memory offset of r>)
* }
* function f_1(a)
* {
* ...body of f...
* mstore(<memory offset of r>, mload(<memory offset of b>))
* ...body of f continued...
* }
*
* Prerequisite: Disambiguator, ForLoopInitRewriter, FunctionHoister.
*/
@ -98,6 +147,7 @@ public:
void operator()(FunctionDefinition& _functionDefinition) override;
void operator()(Block& _block) override;
using ASTModifier::visit;
void visit(Expression& _expression) override;
private:
class VariableMemoryOffsetTracker
@ -113,20 +163,36 @@ private:
/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal
/// or std::nullopt if the variable should not be moved.
std::optional<YulString> operator()(YulString _variable) const;
/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal
/// or std::nullopt if the variable should not be moved.
std::optional<YulString> operator()(TypedName const& _variable) const;
/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal
/// or std::nullopt if the variable should not be moved.
std::optional<YulString> operator()(Identifier const& _variable) const;
private:
u256 m_reservedMemory;
std::map<YulString, uint64_t> const& m_memorySlots;
uint64_t m_numRequiredSlots = 0;
};
struct FunctionMoveInfo
{
std::vector<std::optional<YulString>> returnVariableSlots;
};
StackToMemoryMover(
OptimiserStepContext& _context,
VariableMemoryOffsetTracker const& _memoryOffsetTracker
VariableMemoryOffsetTracker const& _memoryOffsetTracker,
std::map<YulString, std::vector<TypedName>> _functionReturnVariables
);
OptimiserStepContext& m_context;
VariableMemoryOffsetTracker const& m_memoryOffsetTracker;
NameDispenser& m_nameDispenser;
/// Map from function names to the return variables of the function with that name.
std::map<YulString, std::vector<TypedName>> m_functionReturnVariables;
/// List of functions generated while running this step that are to be appended to the code in the end.
std::list<Statement> m_newFunctionDefinitions;
};
}

View File

@ -345,6 +345,10 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(
{
YulString originalFunctionName = m_currentFunction;
m_currentFunction = _function.name;
for (TypedName const& _argument: _function.parameters)
visitVariableName(_argument.name);
for (TypedName const& _argument: _function.returnVariables)
visitVariableName(_argument.name);
ASTWalker::operator()(_function);
m_currentFunction = originalFunctionName;
}

View File

@ -0,0 +1,29 @@
{
function f(x, y) {
mstore(0x80, x)
if calldataload(0) { sstore(y, y) }
}
}
// ====
// stackOptimization: true
// ----
// PUSH1 0x17
// JUMP
// JUMPDEST
// DUP1
// PUSH1 0x80
// MSTORE
// POP
// PUSH1 0x0
// CALLDATALOAD
// ISZERO
// PUSH1 0x13
// JUMPI
// DUP1
// DUP2
// SSTORE
// JUMPDEST
// POP
// JUMPDEST
// JUMP
// JUMPDEST

View File

@ -0,0 +1,110 @@
{
let x := 7
let y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15 := verbatim_0i_15o("\x60\x42") // the verbatim will show up as PUSH1 42
// last use of x - the slot of x will be marked as unused, but not popped, since it is not at the stack top
sstore(0,x)
// If the slot of x is blindly reused, this will fail.
let z1, z2 := verbatim_0i_2o("\x60\x43") // will show up as PUSH1 43
// prevent the z's from being popped immediately after their declaration above.
mstore(1, z1)
mstore(1, z2)
// use all y's to prevent them from being popped immediately after their declaration above
sstore(1, y1)
sstore(1, y2)
sstore(1, y3)
sstore(1, y4)
sstore(1, y5)
sstore(1, y6)
sstore(1, y7)
sstore(1, y8)
sstore(1, y9)
sstore(1, y10)
sstore(1, y11)
sstore(1, y12)
sstore(1, y13)
sstore(1, y14)
sstore(1, y15)
}
// ====
// stackOptimization: true
// ----
// PUSH1 0x7
// PUSH1 0x42
// DUP16
// PUSH1 0x0
// SSTORE
// PUSH1 0x43
// DUP2
// PUSH1 0x1
// MSTORE
// DUP1
// PUSH1 0x1
// MSTORE
// POP
// POP
// DUP15
// PUSH1 0x1
// SSTORE
// DUP14
// PUSH1 0x1
// SSTORE
// DUP13
// PUSH1 0x1
// SSTORE
// DUP12
// PUSH1 0x1
// SSTORE
// DUP11
// PUSH1 0x1
// SSTORE
// DUP10
// PUSH1 0x1
// SSTORE
// DUP9
// PUSH1 0x1
// SSTORE
// DUP8
// PUSH1 0x1
// SSTORE
// DUP7
// PUSH1 0x1
// SSTORE
// DUP6
// PUSH1 0x1
// SSTORE
// DUP5
// PUSH1 0x1
// SSTORE
// DUP4
// PUSH1 0x1
// SSTORE
// DUP3
// PUSH1 0x1
// SSTORE
// DUP2
// PUSH1 0x1
// SSTORE
// DUP1
// PUSH1 0x1
// SSTORE
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP
// POP

View File

@ -15,6 +15,9 @@
// mstore(0x00, 0)
// sstore(0, mload(0x00))
// function h($hx) -> y
// { y := $hx }
// {
// mstore(0x20, $hx)
// y := mload(0x20)
// }
// sstore(1, h(32))
// }

View File

@ -0,0 +1,21 @@
{
mstore(0x40, memoryguard(0x60))
{
let x, y
}
{
let z, $w
}
}
// ----
// step: fakeStackLimitEvader
//
// {
// mstore(0x40, memoryguard(0x80))
// { let x, y }
// {
// let z_1, $w_2
// mstore(0x60, $w_2)
// let z := z_1
// }
// }

View File

@ -0,0 +1,48 @@
{
{
mstore(0x40, memoryguard(0x80))
let a1, a2 := f()
sstore(a1, a2)
}
function g(x) -> a, b { a := x b := 2 }
function f() -> $b1, $b2 {
if calldataload(0) {
$b1, $b2 := g(1)
leave
}
$b1, $b2 := g(2)
}
}
// ----
// step: fakeStackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0xc0))
// f()
// let a2 := mload(0x80)
// let a1 := mload(0xa0)
// sstore(a1, a2)
// }
// function g(x) -> a, b
// {
// a := x
// b := 2
// }
// function f()
// {
// mstore(0xa0, 0)
// mstore(0x80, 0)
// if calldataload(0)
// {
// let $b1_1, $b2_2 := g(1)
// mstore(0x80, $b2_2)
// mstore(0xa0, $b1_1)
// leave
// }
// let $b1_3, $b2_4 := g(2)
// mstore(0x80, $b2_4)
// mstore(0xa0, $b1_3)
// }
// }

View File

@ -0,0 +1,38 @@
{
{
mstore(0x40, memoryguard(0x80))
sstore(0, f())
}
function f() -> $b1 {
if calldataload(0) {
$b1 := 0
leave
}
$b1 := 1
}
}
// ----
// step: fakeStackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0xa0))
// sstore(0, f())
// }
// function f() -> $b1
// {
// mstore(0x80, 0)
// f_1()
// $b1 := mload(0x80)
// }
// function f_1()
// {
// if calldataload(0)
// {
// mstore(0x80, 0)
// leave
// }
// mstore(0x80, 1)
// }
// }

View File

@ -0,0 +1,38 @@
{
{
mstore(0x40, memoryguard(0x80))
sstore(0, f(1,2,3))
}
function f(a, b, c) -> $b1 {
if calldataload(add(sub(a,b),c)) {
$b1 := 0
leave
}
$b1 := 1
}
}
// ----
// step: fakeStackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0xa0))
// sstore(0, f(1, 2, 3))
// }
// function f(a_2, b_3, c_4) -> $b1
// {
// mstore(0x80, 0)
// f_1(a_2, b_3, c_4)
// $b1 := mload(0x80)
// }
// function f_1(a, b, c)
// {
// if calldataload(add(sub(a, b), c))
// {
// mstore(0x80, 0)
// leave
// }
// mstore(0x80, 1)
// }
// }

View File

@ -0,0 +1,17 @@
{
function f(x) -> y { y := x }
mstore(0x40, memoryguard(0))
let $z := 42
$z := f($z)
}
// ----
// step: fakeStackLimitEvader
//
// {
// function f(x) -> y
// { y := x }
// mstore(0x40, memoryguard(0x20))
// mstore(0x00, 42)
// mstore(0x00, f(mload(0x00)))
// }

View File

@ -50,6 +50,7 @@
// }
// function f(a1) -> v
// {
// mstore(0x80, a1)
// let a2 := calldataload(mul(2, 4))
// let a3 := calldataload(mul(3, 4))
// let a4 := calldataload(mul(4, 4))
@ -66,7 +67,7 @@
// let a15 := calldataload(mul(15, 4))
// let a16 := calldataload(mul(16, 4))
// let a17 := calldataload(mul(17, 4))
// sstore(0, a1)
// sstore(0, mload(0x80))
// sstore(mul(17, 4), a17)
// sstore(mul(16, 4), a16)
// sstore(mul(15, 4), a15)
@ -83,6 +84,6 @@
// sstore(mul(4, 4), a4)
// sstore(mul(3, 4), a3)
// sstore(mul(2, 4), a2)
// sstore(mul(1, 4), a1)
// sstore(mul(1, 4), mload(0x80))
// }
// }

View File

@ -1,13 +1,12 @@
{
{
mstore(0x40, memoryguard(128))
sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), f())
sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17), f())
}
function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v {
// Should be, but cannot yet be escalated.
v := b16
function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17) -> v {
v := add(b16, b17)
}
function f() -> v{
function f() -> v {
let a1 := calldataload(mul(1,4))
let a2 := calldataload(mul(2,4))
let a3 := calldataload(mul(3,4))
@ -52,10 +51,10 @@
// {
// {
// mstore(0x40, memoryguard(0xa0))
// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), f())
// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), f())
// }
// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v
// { v := b16 }
// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17) -> v
// { v := add(b16, b17) }
// function f() -> v_1
// {
// mstore(0x80, calldataload(mul(1, 4)))

View File

@ -0,0 +1,55 @@
{
{
mstore(0x40, memoryguard(128))
sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29), 0)
}
function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29) -> v {
sstore(0, b14)
sstore(1, b15)
sstore(2, b16)
sstore(3, b17)
sstore(4, b18)
sstore(5, b19)
sstore(6, b29)
v := add(b1,b29)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x02c0))
// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29), 0)
// }
// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29) -> v
// {
// mstore(0x02a0, b1)
// mstore(0x0280, b2)
// mstore(0x0260, b3)
// mstore(0x0240, b4)
// mstore(0x0220, b5)
// mstore(0x0200, b6)
// mstore(0x01e0, b7)
// mstore(0x01c0, b8)
// mstore(0x01a0, b9)
// mstore(0x0180, b10)
// mstore(0x0160, b11)
// mstore(0x0140, b12)
// mstore(0x0120, b13)
// mstore(0x0100, b14)
// mstore(0xc0, b17)
// mstore(0xa0, b18)
// mstore(0x80, b19)
// mstore(0xe0, b29)
// sstore(0, mload(0x0100))
// sstore(1, b15)
// sstore(2, b16)
// sstore(3, mload(0xc0))
// sstore(4, mload(0xa0))
// sstore(5, mload(0x80))
// sstore(6, mload(0xe0))
// v := add(mload(0x02a0), mload(0xe0))
// }
// }

View File

@ -0,0 +1,61 @@
{
{
mstore(0x40, memoryguard(128))
sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29, 30), 0)
}
function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> v {
sstore(0, b14)
sstore(1, b15)
sstore(2, b16)
sstore(3, b17)
sstore(4, b18)
sstore(5, b19)
sstore(6, b29)
sstore(7, b30)
v := b30
sstore(b1, b30)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0300))
// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30), 0)
// }
// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> v
// {
// mstore(0x02e0, b1)
// mstore(0x02c0, b2)
// mstore(0x02a0, b3)
// mstore(0x0280, b4)
// mstore(0x0260, b5)
// mstore(0x0240, b6)
// mstore(0x0220, b7)
// mstore(0x0200, b8)
// mstore(0x01e0, b9)
// mstore(0x01c0, b10)
// mstore(0x01a0, b11)
// mstore(0x0180, b12)
// mstore(0x0160, b13)
// mstore(0x0140, b14)
// mstore(0x0120, b15)
// mstore(0xc0, b17)
// mstore(0xa0, b18)
// mstore(0x80, b19)
// mstore(0xe0, b29)
// mstore(0x0100, b30)
// sstore(0, mload(0x0140))
// sstore(1, mload(0x0120))
// sstore(2, b16)
// sstore(3, mload(0xc0))
// sstore(4, mload(0xa0))
// sstore(5, mload(0x80))
// sstore(6, mload(0xe0))
// sstore(7, mload(0x0100))
// v := mload(0x0100)
// sstore(mload(0x02e0), mload(0x0100))
// }
// }

View File

@ -0,0 +1,63 @@
{
{
mstore(0x40, memoryguard(128))
sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), 0)
}
function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> v {
sstore(0, b14)
sstore(1, b15)
sstore(2, b16)
sstore(3, b17)
sstore(4, b18)
sstore(5, b19)
sstore(6, b29)
sstore(7, b30)
sstore(8, b31)
v := add(b1,b31)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0340))
// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31), 0)
// }
// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> v
// {
// mstore(0x0320, b1)
// mstore(0x0300, b2)
// mstore(0x02e0, b3)
// mstore(0x02c0, b4)
// mstore(0x02a0, b5)
// mstore(0x0280, b6)
// mstore(0x0260, b7)
// mstore(0x0240, b8)
// mstore(0x0220, b9)
// mstore(0x0200, b10)
// mstore(0x01e0, b11)
// mstore(0x01c0, b12)
// mstore(0x01a0, b13)
// mstore(0x0180, b14)
// mstore(0x0160, b15)
// mstore(0x0140, b16)
// mstore(0xc0, b17)
// mstore(0xa0, b18)
// mstore(0x80, b19)
// mstore(0xe0, b29)
// mstore(0x0120, b30)
// mstore(0x0100, b31)
// sstore(0, mload(0x0180))
// sstore(1, mload(0x0160))
// sstore(2, mload(0x0140))
// sstore(3, mload(0xc0))
// sstore(4, mload(0xa0))
// sstore(5, mload(0x80))
// sstore(6, mload(0xe0))
// sstore(7, mload(0x0120))
// sstore(8, mload(0x0100))
// v := add(mload(0x0320), mload(0x0100))
// }
// }

View File

@ -0,0 +1,48 @@
{
{
mstore(0x40, memoryguard(128))
let a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
sstore(0, 1)
a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
}
function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 {
b1 := 1
b2 := 2
b15 := 15
sstore(b16, b30)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0260))
// let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
// sstore(0, 1)
// a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
// }
// function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15
// {
// mstore(0x0240, b16)
// mstore(0x0220, b17)
// mstore(0x0200, b18)
// mstore(0x01e0, b19)
// mstore(0x01c0, b20)
// mstore(0x01a0, b21)
// mstore(0x0180, b22)
// mstore(0x0160, b23)
// mstore(0x0140, b24)
// mstore(0x0120, b25)
// mstore(0x0100, b26)
// mstore(0xe0, b27)
// mstore(0xc0, b28)
// mstore(0xa0, b29)
// mstore(0x80, b30)
// b1 := 1
// b2 := 2
// b15 := 15
// sstore(mload(0x0240), mload(0x80))
// }
// }

View File

@ -0,0 +1,49 @@
{
{
mstore(0x40, memoryguard(128))
let a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)
sstore(0, 1)
a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)
}
function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 {
b1 := 1
b2 := 2
b15 := 15
sstore(b16, b31)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0280))
// let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)
// sstore(0, 1)
// a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)
// }
// function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15
// {
// mstore(0x0260, b16)
// mstore(0x0240, b17)
// mstore(0x0220, b18)
// mstore(0x0200, b19)
// mstore(0x01e0, b20)
// mstore(0x01c0, b21)
// mstore(0x01a0, b22)
// mstore(0x0180, b23)
// mstore(0x0160, b24)
// mstore(0x0140, b25)
// mstore(0x0120, b26)
// mstore(0x0100, b27)
// mstore(0xe0, b28)
// mstore(0xc0, b29)
// mstore(0xa0, b30)
// mstore(0x80, b31)
// b1 := 1
// b2 := 2
// b15 := 15
// sstore(mload(0x0260), mload(0x80))
// }
// }

View File

@ -0,0 +1,55 @@
{
{
mstore(0x40, memoryguard(128))
let a_1 := 1
let a_2 := 2
let a_3 := 3
let a_4 := 4
let a_5 := 5
let a_6 := 6
let a_7 := 7
let a_8 := 8
let a_9 := 9
let a_10 := 10
let a_11 := 11
let a_12 := 12
let a_13 := 13
let a_14 := 14
let a_15 := 15
let a_16 := 16
let a_17 := 17
let a_18 := 18
let a_19 := 19
let a_20 := 20
verbatim_20i_0o("test", a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10, a_11, a_12, a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0200))
// mstore(0x80, 1)
// mstore(0xa0, 2)
// mstore(0xc0, 3)
// mstore(0xe0, 4)
// mstore(0x0100, 5)
// mstore(0x0120, 6)
// mstore(0x0140, 7)
// mstore(0x0160, 8)
// mstore(0x0180, 9)
// mstore(0x01c0, 10)
// mstore(0x01a0, 11)
// mstore(0x01e0, 12)
// let a_13 := 13
// let a_14 := 14
// let a_15 := 15
// let a_16 := 16
// let a_17 := 17
// let a_18 := 18
// let a_19 := 19
// let a_20 := 20
// verbatim_20i_0o("test", mload(0x80), mload(0xa0), mload(0xc0), mload(0xe0), mload(0x0100), mload(0x0120), mload(0x0140), mload(0x0160), mload(0x0180), mload(0x01c0), mload(0x01a0), mload(0x01e0), a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20)
// }
// }

View File

@ -0,0 +1,116 @@
{
{
mstore(0x40, memoryguard(128))
let a_1 := 1
let a_2 := 2
let a_3 := 3
let a_4 := 4
let a_5 := 5
let a_6 := 6
let a_7 := 7
let a_8 := 8
let a_9 := 9
let a_10 := 10
let a_11 := 11
let a_12 := 12
let a_13 := 13
let a_14 := 14
let a_15 := 15
let a_16 := 16
let a_17 := 17
let a_18 := 18
let a_19 := 19
let a_20 := 20
let b_1, b_2, b_3, b_4, b_5, b_6, b_7, b_8, b_9, b_10, b_11, b_12, b_13, b_14, b_15, b_16, b_17, b_18, b_19, b_20 := verbatim_20i_20o("test", a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10, a_11, a_12, a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20)
sstore(1, b_1)
sstore(2, b_2)
sstore(3, b_3)
sstore(4, b_4)
sstore(5, b_5)
sstore(6, b_6)
sstore(7, b_7)
sstore(8, b_8)
sstore(9, b_9)
sstore(10, b_10)
sstore(11, b_11)
sstore(12, b_12)
sstore(13, b_13)
sstore(14, b_14)
sstore(15, b_15)
sstore(16, b_16)
sstore(17, b_17)
sstore(18, b_18)
sstore(19, b_19)
sstore(20, b_20)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0x0280))
// mstore(0x80, 1)
// mstore(0xa0, 2)
// mstore(0xc0, 3)
// mstore(0xe0, 4)
// mstore(0x0100, 5)
// mstore(0x0120, 6)
// mstore(0x0140, 7)
// mstore(0x0160, 8)
// mstore(0x0180, 9)
// mstore(0x0240, 10)
// mstore(0x0220, 11)
// mstore(0x0260, 12)
// let a_13 := 13
// let a_14 := 14
// let a_15 := 15
// let a_16 := 16
// let a_17 := 17
// let a_18 := 18
// let a_19 := 19
// let a_20 := 20
// let b_1_1, b_2_2, b_3_3, b_4_4, b_5_5, b_6_6, b_7_7, b_8_8, b_9_9, b_10_10, b_11_11, b_12_12, b_13_13, b_14_14, b_15_15, b_16_16, b_17_17, b_18_18, b_19_19, b_20_20 := verbatim_20i_20o("test", mload(0x80), mload(0xa0), mload(0xc0), mload(0xe0), mload(0x0100), mload(0x0120), mload(0x0140), mload(0x0160), mload(0x0180), mload(0x0240), mload(0x0220), mload(0x0260), a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20)
// mstore(0x01a0, b_4_4)
// mstore(0x01c0, b_3_3)
// mstore(0x01e0, b_2_2)
// mstore(0x0200, b_1_1)
// let b_20 := b_20_20
// let b_19 := b_19_19
// let b_18 := b_18_18
// let b_17 := b_17_17
// let b_16 := b_16_16
// let b_15 := b_15_15
// let b_14 := b_14_14
// let b_13 := b_13_13
// let b_12 := b_12_12
// let b_11 := b_11_11
// let b_10 := b_10_10
// let b_9 := b_9_9
// let b_8 := b_8_8
// let b_7 := b_7_7
// let b_6 := b_6_6
// let b_5 := b_5_5
// sstore(1, mload(0x0200))
// sstore(2, mload(0x01e0))
// sstore(3, mload(0x01c0))
// sstore(4, mload(0x01a0))
// sstore(5, b_5)
// sstore(6, b_6)
// sstore(7, b_7)
// sstore(8, b_8)
// sstore(9, b_9)
// sstore(10, b_10)
// sstore(11, b_11)
// sstore(12, b_12)
// sstore(13, b_13)
// sstore(14, b_14)
// sstore(15, b_15)
// sstore(16, b_16)
// sstore(17, b_17)
// sstore(18, b_18)
// sstore(19, b_19)
// sstore(20, b_20)
// }
// }

View File

@ -0,0 +1,53 @@
{
{
mstore(0x40, memoryguard(128))
let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a13, a14, a15, a16, a17, a18 := verbatim_0i_16o("test")
a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a13, a14, a15, a16, a17, a18 := verbatim_0i_16o("test")
sstore(a1, 10)
sstore(a18, 20)
}
}
// ----
// step: stackLimitEvader
//
// {
// {
// mstore(0x40, memoryguard(0xa0))
// let a1_1, a2_2, a3_3, a4_4, a5_5, a6_6, a7_7, a8_8, a9_9, a10_10, a13_11, a14_12, a15_13, a16_14, a17_15, a18_16 := verbatim_0i_16o("test")
// mstore(0x80, a1_1)
// let a18 := a18_16
// let a17 := a17_15
// let a16 := a16_14
// let a15 := a15_13
// let a14 := a14_12
// let a13 := a13_11
// let a10 := a10_10
// let a9 := a9_9
// let a8 := a8_8
// let a7 := a7_7
// let a6 := a6_6
// let a5 := a5_5
// let a4 := a4_4
// let a3 := a3_3
// let a2 := a2_2
// let a1_17, a2_18, a3_19, a4_20, a5_21, a6_22, a7_23, a8_24, a9_25, a10_26, a13_27, a14_28, a15_29, a16_30, a17_31, a18_32 := verbatim_0i_16o("test")
// mstore(0x80, a1_17)
// a18 := a18_32
// a17 := a17_31
// a16 := a16_30
// a15 := a15_29
// a14 := a14_28
// a13 := a13_27
// a10 := a10_26
// a9 := a9_25
// a8 := a8_24
// a7 := a7_23
// a6 := a6_22
// a5 := a5_21
// a4 := a4_20
// a3 := a3_19
// a2 := a2_18
// sstore(mload(0x80), 10)
// sstore(a18, 20)
// }
// }