Reuse function argument slots and defer allocation of return variable slots in EVMCodeTransform.

This commit is contained in:
Daniel Kirchner 2021-02-04 21:52:07 +01:00
parent 04a051d380
commit 4b0f8383a6
13 changed files with 337 additions and 102 deletions

View File

@ -13,6 +13,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:

View File

@ -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,16 @@ 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.
if (!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 +407,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 +422,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 +431,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 +461,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 +482,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 +594,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 +608,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 +634,59 @@ 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;
}
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 +694,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 +722,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 +743,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 +801,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.");

View File

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

View File

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

View File

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

View File

@ -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: 3 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 g: 5 f: 5 ");
BOOST_CHECK_EQUAL(out, "h: 9 f: 3 ");
}
BOOST_AUTO_TEST_CASE(nested)
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: 3 f: 5 ");
}
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: 3 : 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()

View File

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

View File

@ -7,8 +7,8 @@
// PUSH1 0x8
// JUMP
// JUMPDEST
// POP
// POP
// JUMPDEST
// POP
// POP
// JUMP
// JUMPDEST

View File

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

View File

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

View File

@ -14,19 +14,19 @@
// JUMP
// JUMPDEST
// PUSH1 0x0
// DUP2
// SWAP2
// POP
// DUP1
// POP
// PUSH1 0x3
// SWAP2
// POP
// DUP2
// SWAP1
// POP
// JUMPDEST
// SWAP3
// DUP1
// SWAP2
// POP
// POP
// JUMPDEST
// SWAP1
// JUMP
// JUMPDEST
// PUSH1 0x7

View File

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

View File

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