Further customize max swap/dup.

This commit is contained in:
Daniel Kirchner 2022-12-05 14:24:32 +01:00
parent a467bdcb3c
commit 3ff457c312
17 changed files with 549 additions and 68 deletions

View File

@ -433,7 +433,7 @@ map<u256, u256> const& Assembly::optimiseInternal(
while (iter != m_items.end()) while (iter != m_items.end())
{ {
KnownState emptyState; KnownState emptyState;
CommonSubexpressionEliminator eliminator{emptyState}; CommonSubexpressionEliminator eliminator{emptyState, maxSwap(), maxDup()};
auto orig = iter; auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end(), usesMSize); iter = eliminator.feedItems(iter, m_items.end(), usesMSize);
bool shouldReplace = false; bool shouldReplace = false;

View File

@ -62,7 +62,7 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
for (int height = minHeight; height <= m_state.stackHeight(); ++height) for (int height = minHeight; height <= m_state.stackHeight(); ++height)
targetStackContents[height] = m_state.stackElement(height, SourceLocation()); targetStackContents[height] = m_state.stackElement(height, SourceLocation());
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode( AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations, m_maxSwap, m_maxDup).generateCode(
m_initialState.sequenceNumber(), m_initialState.sequenceNumber(),
m_initialState.stackHeight(), m_initialState.stackHeight(),
initialStackContents, initialStackContents,
@ -125,9 +125,11 @@ void CommonSubexpressionEliminator::optimizeBreakingItem()
CSECodeGenerator::CSECodeGenerator( CSECodeGenerator::CSECodeGenerator(
ExpressionClasses& _expressionClasses, ExpressionClasses& _expressionClasses,
vector<CSECodeGenerator::StoreOperation> const& _storeOperations vector<CSECodeGenerator::StoreOperation> const& _storeOperations,
unsigned _maxSwap,
unsigned _maxDup
): ):
m_expressionClasses(_expressionClasses) m_expressionClasses(_expressionClasses), m_maxSwap(_maxSwap), m_maxDup(_maxDup)
{ {
for (auto const& store: _storeOperations) for (auto const& store: _storeOperations)
m_storeOperations[make_pair(store.target, store.slot)].push_back(store); m_storeOperations[make_pair(store.target, store.slot)].push_back(store);
@ -472,7 +474,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, SourceLocation const& _locat
{ {
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); assertThrow(_fromPosition != c_invalidPosition, OptimizerException, "");
int instructionNum = 1 + m_stackHeight - _fromPosition; int instructionNum = 1 + m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString); assertThrow(instructionNum <= static_cast<int>(m_maxDup), StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(AssemblyItemType::Dup, instructionNum, _location)); appendItem(AssemblyItem(AssemblyItemType::Dup, instructionNum, _location));
m_stack[m_stackHeight] = m_stack[_fromPosition]; m_stack[m_stackHeight] = m_stack[_fromPosition];
@ -485,7 +487,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons
if (_fromPosition == m_stackHeight) if (_fromPosition == m_stackHeight)
return; return;
int instructionNum = m_stackHeight - _fromPosition; int instructionNum = m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString); assertThrow(instructionNum <= static_cast<int>(m_maxSwap), StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(AssemblyItemType::Swap, instructionNum, _location)); appendItem(AssemblyItem(AssemblyItemType::Swap, instructionNum, _location));

View File

@ -66,7 +66,7 @@ public:
using Id = ExpressionClasses::Id; using Id = ExpressionClasses::Id;
using StoreOperation = KnownState::StoreOperation; using StoreOperation = KnownState::StoreOperation;
explicit CommonSubexpressionEliminator(KnownState const& _state): m_initialState(_state), m_state(_state) {} explicit CommonSubexpressionEliminator(KnownState const& _state, unsigned _maxSwap, unsigned _maxDup): m_initialState(_state), m_state(_state), m_maxSwap(_maxSwap), m_maxDup(_maxDup) {}
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
/// item that must be fed into a new instance of the eliminator. /// item that must be fed into a new instance of the eliminator.
@ -93,6 +93,8 @@ private:
/// The item that breaks the basic block, can be nullptr. /// The item that breaks the basic block, can be nullptr.
/// It is usually appended to the block but can be optimized in some cases. /// It is usually appended to the block but can be optimized in some cases.
AssemblyItem const* m_breakingItem = nullptr; AssemblyItem const* m_breakingItem = nullptr;
unsigned m_maxSwap = 16;
unsigned m_maxDup = 16;
}; };
/** /**
@ -108,7 +110,7 @@ public:
/// Initializes the code generator with the given classes and store operations. /// Initializes the code generator with the given classes and store operations.
/// The store operations have to be sorted by sequence number in ascending order. /// The store operations have to be sorted by sequence number in ascending order.
CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations, unsigned _maxSwap, unsigned _maxDup);
/// @returns the assembly items generated from the given requirements /// @returns the assembly items generated from the given requirements
/// @param _initialSequenceNumber starting sequence number, do not generate sequenced operations /// @param _initialSequenceNumber starting sequence number, do not generate sequenced operations
@ -169,6 +171,8 @@ private:
/// The set of equivalence classes that should be present on the stack at the end. /// The set of equivalence classes that should be present on the stack at the end.
std::set<Id> m_finalClasses; std::set<Id> m_finalClasses;
std::map<int, Id> m_targetStack; std::map<int, Id> m_targetStack;
unsigned m_maxSwap = 16;
unsigned m_maxDup = 16;
}; };
template <class AssemblyItemIterator> template <class AssemblyItemIterator>

View File

@ -475,7 +475,7 @@ void CompilerUtils::encodeToMemory(
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++; dynPointers++;
assertThrow( assertThrow(
(argSize + dynPointers) < 16, (argSize + dynPointers) < m_context.assembly().maxSwap(),
StackTooDeepError, StackTooDeepError,
util::stackTooDeepString util::stackTooDeepString
); );
@ -536,7 +536,7 @@ void CompilerUtils::encodeToMemory(
{ {
// copy tail pointer (=mem_end - mem_start) to memory // copy tail pointer (=mem_end - mem_start) to memory
assertThrow( assertThrow(
(2 + dynPointers) <= 16, (2 + dynPointers) <= m_context.assembly().maxDup(),
StackTooDeepError, StackTooDeepError,
util::stackTooDeepString util::stackTooDeepString
); );
@ -1415,7 +1415,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
unsigned const size = _variable.annotation().type->sizeOnStack(); unsigned const size = _variable.annotation().type->sizeOnStack();
solAssert(stackPosition >= size, "Variable size and position mismatch."); solAssert(stackPosition >= size, "Variable size and position mismatch.");
// move variable starting from its top end in the stack // move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16) if (stackPosition - size + 1 > m_context.assembly().maxSwap())
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_variable.location()) << errinfo_sourceLocation(_variable.location()) <<
@ -1428,7 +1428,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{ {
assertThrow( assertThrow(
_stackDepth <= 16, _stackDepth <= m_context.assembly().maxDup(),
StackTooDeepError, StackTooDeepError,
util::stackTooDeepString util::stackTooDeepString
); );
@ -1454,7 +1454,7 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
void CompilerUtils::rotateStackUp(unsigned _items) void CompilerUtils::rotateStackUp(unsigned _items)
{ {
assertThrow( assertThrow(
_items - 1 <= 16, _items - 1 <= m_context.assembly().maxSwap(),
StackTooDeepError, StackTooDeepError,
util::stackTooDeepString util::stackTooDeepString
); );
@ -1465,7 +1465,7 @@ void CompilerUtils::rotateStackUp(unsigned _items)
void CompilerUtils::rotateStackDown(unsigned _items) void CompilerUtils::rotateStackDown(unsigned _items)
{ {
assertThrow( assertThrow(
_items - 1 <= 16, _items - 1 <= m_context.assembly().maxSwap(),
StackTooDeepError, StackTooDeepError,
util::stackTooDeepString util::stackTooDeepString
); );

View File

@ -48,7 +48,7 @@ StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclarat
void StackVariable::retrieveValue(SourceLocation const& _location, bool) const void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
{ {
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset); unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory if (stackPos + 1 > m_context.assembly().maxDup()) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
@ -62,15 +62,17 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const
{ {
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
if (stackDiff > 16) if (stackDiff > 0)
BOOST_THROW_EXCEPTION( {
StackTooDeepError() << if (stackDiff > m_context.assembly().maxSwap())
errinfo_sourceLocation(_location) << BOOST_THROW_EXCEPTION(
util::errinfo_comment(util::stackTooDeepString) StackTooDeepError() <<
); errinfo_sourceLocation(_location) <<
else if (stackDiff > 0) util::errinfo_comment(util::stackTooDeepString)
);
for (unsigned i = 0; i < m_size; ++i) for (unsigned i = 0; i < m_size; ++i)
m_context << AssemblyItem(AssemblyItemType::Swap, stackDiff) << Instruction::POP; m_context << AssemblyItem(AssemblyItemType::Swap, stackDiff) << Instruction::POP;
}
if (!_move) if (!_move)
retrieveValue(_location); retrieveValue(_location);
} }

View File

@ -180,7 +180,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
bool foundUnusedSlot = false; bool foundUnusedSlot = false;
for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it) for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it)
{ {
if (m_assembly.stackHeight() - *it > 17) if (m_assembly.stackHeight() - *it > static_cast<int>(m_assembly.maxSwap() + 1))
continue; continue;
foundUnusedSlot = true; foundUnusedSlot = true;
auto slot = static_cast<size_t>(*it); auto slot = static_cast<size_t>(*it);
@ -454,16 +454,16 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
&std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name)) &std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name))
)) = static_cast<int>(n); )) = static_cast<int>(n);
if (stackLayout.size() > 17) if (stackLayout.size() > m_assembly.maxSwap() + 1)
{ {
StackTooDeepError error( StackTooDeepError error(
_function.name, _function.name,
YulString{}, YulString{},
static_cast<int>(stackLayout.size()) - 17, static_cast<int>(stackLayout.size()) - static_cast<int>(m_assembly.maxSwap() + 1),
"The function " + "The function " +
_function.name.str() + _function.name.str() +
" has " + " has " +
to_string(stackLayout.size() - 17) + to_string(stackLayout.size() - (m_assembly.maxSwap() + 1)) +
" parameters or return variables too many to fit the stack size." " parameters or return variables too many to fit the stack size."
); );
stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size())); stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size()));
@ -777,7 +777,7 @@ size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString
yulAssert(m_context->variableStackHeights.count(&_var), ""); yulAssert(m_context->variableStackHeights.count(&_var), "");
size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var]; size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var];
yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable."); yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
size_t limit = _forSwap ? 17 : 16; size_t limit = _forSwap ? m_assembly.maxSwap() + 1 : m_assembly.maxDup();
if (heightDiff > limit) if (heightDiff > limit)
{ {
m_stackErrors.emplace_back( m_stackErrors.emplace_back(

View File

@ -359,7 +359,9 @@ void OptimizedEVMCodeTransform::createStackLayout(std::shared_ptr<DebugData cons
[&]() [&]()
{ {
m_assembly.appendInstruction(evmasm::Instruction::POP); m_assembly.appendInstruction(evmasm::Instruction::POP);
} },
m_assembly.maxSwap(),
m_assembly.maxDup()
); );
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), ""); yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
} }

View File

@ -127,10 +127,10 @@ private:
static bool dupDeepSlotIfRequired(ShuffleOperations& _ops) static bool dupDeepSlotIfRequired(ShuffleOperations& _ops)
{ {
// Check if the stack is large enough for anything to potentially become unreachable. // Check if the stack is large enough for anything to potentially become unreachable.
if (_ops.sourceSize() < 15) if (_ops.sourceSize() < (_ops.maxDup() - 1))
return false; return false;
// Check whether any deep slot might still be needed later (i.e. we still need to reach it with a DUP or SWAP). // Check whether any deep slot might still be needed later (i.e. we still need to reach it with a DUP or SWAP).
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - 15)) for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - (_ops.maxDup() - 1)))
{ {
// This slot needs to be moved. // This slot needs to be moved.
if (!_ops.isCompatible(sourceOffset, sourceOffset)) if (!_ops.isCompatible(sourceOffset, sourceOffset))
@ -255,10 +255,10 @@ private:
) )
{ {
// We cannot swap that deep. // We cannot swap that deep.
if (ops.sourceSize() - offset - 1 > 16) if (ops.sourceSize() - offset - 1 > ops.maxSwap())
{ {
// If there is a reachable slot to be removed, park the current top there. // If there is a reachable slot to be removed, park the current top there.
for (size_t swapDepth: ranges::views::iota(1u, 17u) | ranges::views::reverse) for (size_t swapDepth: ranges::views::iota(1u, ops.maxSwap() + 1) | ranges::views::reverse)
if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0) if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0)
{ {
ops.swap(swapDepth); ops.swap(swapDepth);
@ -326,7 +326,7 @@ private:
yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), ""); yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), "");
yulAssert(ops.isCompatible(sourceTop, sourceTop), ""); yulAssert(ops.isCompatible(sourceTop, sourceTop), "");
auto swappableOffsets = ranges::views::iota(size > 17 ? size - 17 : 0u, size); auto swappableOffsets = ranges::views::iota(size > (ops.maxSwap() + 1) ? size - (ops.maxSwap() + 1) : 0u, size);
// If we find a lower slot that is out of position, but also compatible with the top, swap that up. // If we find a lower slot that is out of position, but also compatible with the top, swap that up.
for (size_t offset: swappableOffsets) for (size_t offset: swappableOffsets)
@ -386,7 +386,7 @@ private:
/// its argument to the stack top. /// its argument to the stack top.
/// @a _pop is a function with signature void() that is called when the top most slot is popped. /// @a _pop is a function with signature void() that is called when the top most slot is popped.
template<typename Swap, typename PushOrDup, typename Pop> template<typename Swap, typename PushOrDup, typename Pop>
void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop) void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop, size_t _maxSwap, size_t _maxDup)
{ {
struct ShuffleOperations struct ShuffleOperations
{ {
@ -396,18 +396,24 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
PushOrDup pushOrDupCallback; PushOrDup pushOrDupCallback;
Pop popCallback; Pop popCallback;
std::map<StackSlot, int> multiplicity; std::map<StackSlot, int> multiplicity;
size_t const m_maxSwap = 16;
size_t const m_maxDup = 16;
ShuffleOperations( ShuffleOperations(
Stack& _currentStack, Stack& _currentStack,
Stack const& _targetStack, Stack const& _targetStack,
Swap _swap, Swap _swap,
PushOrDup _pushOrDup, PushOrDup _pushOrDup,
Pop _pop Pop _pop,
size_t _maxSwap,
size_t _maxDup
): ):
currentStack(_currentStack), currentStack(_currentStack),
targetStack(_targetStack), targetStack(_targetStack),
swapCallback(_swap), swapCallback(_swap),
pushOrDupCallback(_pushOrDup), pushOrDupCallback(_pushOrDup),
popCallback(_pop) popCallback(_pop),
m_maxSwap(_maxSwap),
m_maxDup(_maxDup)
{ {
for (auto const& slot: currentStack) for (auto const& slot: currentStack)
--multiplicity[slot]; --multiplicity[slot];
@ -417,6 +423,8 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
else else
++multiplicity[slot]; ++multiplicity[slot];
} }
size_t maxSwap() const { return m_maxSwap; }
size_t maxDup() const { return m_maxDup; }
bool isCompatible(size_t _source, size_t _target) bool isCompatible(size_t _source, size_t _target)
{ {
return return
@ -454,7 +462,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
} }
}; };
Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop); Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop, _maxSwap, _maxDup);
yulAssert(_currentStack.size() == _targetStack.size(), ""); yulAssert(_currentStack.size() == _targetStack.size(), "");
for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack)) for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack))

View File

@ -96,7 +96,7 @@ StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout): m_layout(_layo
namespace namespace
{ {
/// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target. /// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target.
vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source, Stack const& _target) vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source, Stack const& _target, size_t _maxSwap, size_t _maxDup)
{ {
Stack currentStack = _source; Stack currentStack = _source;
vector<StackLayoutGenerator::StackTooDeep> stackTooDeepErrors; vector<StackLayoutGenerator::StackTooDeep> stackTooDeepErrors;
@ -113,9 +113,9 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
_target, _target,
[&](unsigned _i) [&](unsigned _i)
{ {
if (_i > 16) if (_i > _maxSwap)
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
_i - 16, _i - _maxSwap,
getVariableChoices(currentStack | ranges::views::take_last(_i + 1)) getVariableChoices(currentStack | ranges::views::take_last(_i + 1))
}); });
}, },
@ -125,14 +125,16 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
return; return;
if ( if (
auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot); auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot);
depth && *depth >= 16 depth && *depth >= _maxDup
) )
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
*depth - 15, *depth - (_maxDup - 1),
getVariableChoices(currentStack | ranges::views::take_last(*depth + 1)) getVariableChoices(currentStack | ranges::views::take_last(*depth + 1))
}); });
}, },
[&]() {} [&]() {},
_maxSwap,
_maxDup
); );
return stackTooDeepErrors; return stackTooDeepErrors;
} }
@ -142,7 +144,7 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
/// If @a _generateSlotOnTheFly returns true for a slot, this slot should not occur in the ideal stack, but /// If @a _generateSlotOnTheFly returns true for a slot, this slot should not occur in the ideal stack, but
/// rather be generated on the fly during shuffling. /// rather be generated on the fly during shuffling.
template<typename Callable> template<typename Callable>
Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly) Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly, size_t _maxSwap, size_t _maxDup)
{ {
struct PreviousSlot { size_t slot; }; struct PreviousSlot { size_t slot; };
@ -174,11 +176,15 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
std::set<StackSlot> outputs; std::set<StackSlot> outputs;
std::map<StackSlot, int> multiplicity; std::map<StackSlot, int> multiplicity;
Callable generateSlotOnTheFly; Callable generateSlotOnTheFly;
size_t m_maxSwap = 16;
size_t m_maxDup = 16;
ShuffleOperations( ShuffleOperations(
vector<variant<PreviousSlot, StackSlot>>& _layout, vector<variant<PreviousSlot, StackSlot>>& _layout,
Stack const& _post, Stack const& _post,
Callable _generateSlotOnTheFly Callable _generateSlotOnTheFly,
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly) size_t _maxSwap,
size_t _maxDup
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly), m_maxSwap(_maxSwap), m_maxDup(_maxDup)
{ {
for (auto const& layoutSlot: layout) for (auto const& layoutSlot: layout)
if (StackSlot const* slot = get_if<StackSlot>(&layoutSlot)) if (StackSlot const* slot = get_if<StackSlot>(&layoutSlot))
@ -191,6 +197,8 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
if (outputs.count(slot) || generateSlotOnTheFly(slot)) if (outputs.count(slot) || generateSlotOnTheFly(slot))
++multiplicity[slot]; ++multiplicity[slot];
} }
size_t maxSwap() const { return m_maxSwap; }
size_t maxDup() const { return m_maxDup; }
bool isCompatible(size_t _source, size_t _target) bool isCompatible(size_t _source, size_t _target)
{ {
return return
@ -241,7 +249,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
void pop() { layout.pop_back(); } void pop() { layout.pop_back(); }
void pushOrDupTarget(size_t _offset) { layout.push_back(post.at(_offset)); } void pushOrDupTarget(size_t _offset) { layout.push_back(post.at(_offset)); }
}; };
Shuffler<ShuffleOperations>::shuffle(layout, _post, _generateSlotOnTheFly); Shuffler<ShuffleOperations>::shuffle(layout, _post, _generateSlotOnTheFly, _maxSwap, _maxDup);
// Now we can construct the ideal layout before the operation. // Now we can construct the ideal layout before the operation.
// "layout" has shuffled the PreviousSlot{x} to new places using minimal operations to move the operation // "layout" has shuffled the PreviousSlot{x} to new places using minimal operations to move the operation
@ -280,7 +288,7 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG
// Determine the ideal permutation of the slots in _exitLayout that are not operation outputs (and not to be // Determine the ideal permutation of the slots in _exitLayout that are not operation outputs (and not to be
// generated on the fly), s.t. shuffling the `stack + _operation.output` to _exitLayout is cheap. // generated on the fly), s.t. shuffling the `stack + _operation.output` to _exitLayout is cheap.
Stack stack = createIdealLayout(_operation.output, _exitStack, generateSlotOnTheFly); Stack stack = createIdealLayout(_operation.output, _exitStack, generateSlotOnTheFly, maxSwap(), maxDup());
// Make sure the resulting previous slots do not overlap with any assignmed variables. // Make sure the resulting previous slots do not overlap with any assignmed variables.
if (auto const* assignment = get_if<CFG::Assignment>(&_operation.operation)) if (auto const* assignment = get_if<CFG::Assignment>(&_operation.operation))
@ -304,7 +312,7 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG
stack.pop_back(); stack.pop_back();
else if (auto offset = util::findOffset(stack | ranges::views::reverse | ranges::views::drop(1), stack.back())) else if (auto offset = util::findOffset(stack | ranges::views::reverse | ranges::views::drop(1), stack.back()))
{ {
if (*offset + 2 < 16) if (*offset + 2 < maxDup())
stack.pop_back(); stack.pop_back();
else else
break; break;
@ -322,7 +330,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba
for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse) for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse)
{ {
Stack newStack = propagateStackThroughOperation(stack, operation, _aggressiveStackCompression); Stack newStack = propagateStackThroughOperation(stack, operation, _aggressiveStackCompression);
if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack).empty()) if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, maxSwap(), maxDup()).empty())
// If we had stack errors, run again with aggressive stack compression. // If we had stack errors, run again with aggressive stack compression.
return propagateStackThroughBlock(std::move(_exitStack), _block, true); return propagateStackThroughBlock(std::move(_exitStack), _block, true);
stack = std::move(newStack); stack = std::move(newStack);
@ -544,7 +552,7 @@ void StackLayoutGenerator::stitchConditionalJumps(CFG::BasicBlock const& _block)
}); });
} }
Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _stack2) Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _stack2) const
{ {
// TODO: it would be nicer to replace this by a constructive algorithm. // TODO: it would be nicer to replace this by a constructive algorithm.
// Currently it uses a reduced version of the Heap Algorithm to partly brute-force, which seems // Currently it uses a reduced version of the Heap Algorithm to partly brute-force, which seems
@ -580,18 +588,18 @@ Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _sta
auto evaluate = [&](Stack const& _candidate) -> size_t { auto evaluate = [&](Stack const& _candidate) -> size_t {
size_t numOps = 0; size_t numOps = 0;
Stack testStack = _candidate; Stack testStack = _candidate;
auto swap = [&](unsigned _swapDepth) { ++numOps; if (_swapDepth > 16) numOps += 1000; }; auto swap = [&](unsigned _swapDepth) { ++numOps; if (_swapDepth > maxSwap()) numOps += 1000; };
auto dupOrPush = [&](StackSlot const& _slot) auto dupOrPush = [&](StackSlot const& _slot)
{ {
if (canBeFreelyGenerated(_slot)) if (canBeFreelyGenerated(_slot))
return; return;
auto depth = util::findOffset(ranges::concat_view(commonPrefix, testStack) | ranges::views::reverse, _slot); auto depth = util::findOffset(ranges::concat_view(commonPrefix, testStack) | ranges::views::reverse, _slot);
if (depth && *depth >= 16) if (depth && *depth >= maxDup())
numOps += 1000; numOps += 1000;
}; };
createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){}); createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup());
testStack = _candidate; testStack = _candidate;
createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){}); createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup());
return numOps; return numOps;
}; };
@ -642,7 +650,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
{ {
Stack& operationEntry = m_layout.operationEntryLayout.at(&operation); Stack& operationEntry = m_layout.operationEntryLayout.at(&operation);
stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry); stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, maxSwap(), maxDup());
currentStack = operationEntry; currentStack = operationEntry;
for (size_t i = 0; i < operation.input.size(); i++) for (size_t i = 0; i < operation.input.size(); i++)
currentStack.pop_back(); currentStack.pop_back();
@ -656,7 +664,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
[&](CFG::BasicBlock::Jump const& _jump) [&](CFG::BasicBlock::Jump const& _jump)
{ {
Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout; Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout;
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout); stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup());
if (!_jump.backwards) if (!_jump.backwards)
_addChild(_jump.target); _addChild(_jump.target);
@ -667,7 +675,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
m_layout.blockInfos.at(_conditionalJump.zero).entryLayout, m_layout.blockInfos.at(_conditionalJump.zero).entryLayout,
m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout
}) })
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout); stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup());
_addChild(_conditionalJump.zero); _addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero); _addChild(_conditionalJump.nonZero);
@ -679,7 +687,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
return stackTooDeepErrors; return stackTooDeepErrors;
} }
Stack StackLayoutGenerator::compressStack(Stack _stack) Stack StackLayoutGenerator::compressStack(Stack _stack) const
{ {
optional<size_t> firstDupOffset; optional<size_t> firstDupOffset;
do do
@ -697,7 +705,7 @@ Stack StackLayoutGenerator::compressStack(Stack _stack)
break; break;
} }
else if (auto dupDepth = util::findOffset(_stack | ranges::views::reverse | ranges::views::drop(depth + 1), slot)) else if (auto dupDepth = util::findOffset(_stack | ranges::views::reverse | ranges::views::drop(depth + 1), slot))
if (depth + *dupDepth <= 16) if (depth + *dupDepth <= maxDup())
{ {
firstDupOffset = _stack.size() - depth - 1; firstDupOffset = _stack.size() - depth - 1;
break; break;
@ -766,7 +774,7 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::Functi
} }
}; };
auto pop = [&]() { opGas += evmasm::GasMeter::runGas(evmasm::Instruction::POP); }; auto pop = [&]() { opGas += evmasm::GasMeter::runGas(evmasm::Instruction::POP); };
createStackLayout(_source, _target, swap, dupOrPush, pop); createStackLayout(_source, _target, swap, dupOrPush, pop, maxSwap(), maxDup());
return opGas; return opGas;
}; };
/// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from /// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from

View File

@ -100,7 +100,7 @@ private:
/// Calculates the ideal stack layout, s.t. both @a _stack1 and @a _stack2 can be achieved with minimal /// Calculates the ideal stack layout, s.t. both @a _stack1 and @a _stack2 can be achieved with minimal
/// stack shuffling when starting from the returned layout. /// stack shuffling when starting from the returned layout.
static Stack combineStack(Stack const& _stack1, Stack const& _stack2); Stack combineStack(Stack const& _stack1, Stack const& _stack2) const;
/// Walks through the CFG and reports any stack too deep errors that would occur when generating code for it /// Walks through the CFG and reports any stack too deep errors that would occur when generating code for it
/// without countermeasures. /// without countermeasures.
@ -109,7 +109,7 @@ private:
/// @returns a copy of @a _stack stripped of all duplicates and slots that can be freely generated. /// @returns a copy of @a _stack stripped of all duplicates and slots that can be freely generated.
/// Attempts to create a layout that requires a minimal amount of operations to reconstruct the original /// Attempts to create a layout that requires a minimal amount of operations to reconstruct the original
/// stack @a _stack. /// stack @a _stack.
static Stack compressStack(Stack _stack); Stack compressStack(Stack _stack) const;
//// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper. //// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper.
void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr); void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr);

View File

@ -72,7 +72,7 @@ namespace
bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) { bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) {
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode; return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
}); });
evmasm::CommonSubexpressionEliminator cse(_state); evmasm::CommonSubexpressionEliminator cse(_state, 16u, 16u);
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end()); BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
AssemblyItems output = cse.getOptimizedItems(); AssemblyItems output = cse.getOptimizedItems();
@ -107,7 +107,7 @@ namespace
while (iter != _input.end()) while (iter != _input.end())
{ {
KnownState emptyState; KnownState emptyState;
CommonSubexpressionEliminator eliminator{emptyState}; CommonSubexpressionEliminator eliminator{emptyState, 16u, 16u};
auto orig = iter; auto orig = iter;
iter = eliminator.feedItems(iter, _input.end(), usesMSize); iter = eliminator.feedItems(iter, _input.end(), usesMSize);
bool shouldReplace = false; bool shouldReplace = false;
@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
Instruction::ORIGIN Instruction::ORIGIN
}); });
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()}; evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState(), 16u, 16u};
// Make sure CSE breaks after AssignImmutable. // Make sure CSE breaks after AssignImmutable.
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2); BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2);
} }
@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
BOOST_AUTO_TEST_CASE(cse_intermediate_swap) BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
{ {
evmasm::KnownState state; evmasm::KnownState state;
evmasm::CommonSubexpressionEliminator cse(state); evmasm::CommonSubexpressionEliminator cse(state, 16u, 16u);
AssemblyItems input{ AssemblyItems input{
Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1, Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1,
Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1, Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1,

View File

@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE(swap_cycle)
FunctionReturnLabelSlot{function}, JunkSlot{}, JunkSlot{} FunctionReturnLabelSlot{function}, JunkSlot{}, JunkSlot{}
}; };
// Used to hit a swapping cycle. // Used to hit a swapping cycle.
createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){}); createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){}, 16u, 16u);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,24 @@
{
pop(addmod(addmod(0x80000000000000000000000000000000000, 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC, 0x800000000000000000000000000000000000), 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC, 0x8000000000000000000000000000000000000))
}
// ====
// stackOptimization: true
// ----
// /* "":240:279 */
// 0x08000000000000000000000000000000000000
// /* "":172:238 */
// 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
// /* "":131:169 */
// 0x800000000000000000000000000000000000
// /* "":17:170 */
// dup2
// /* "":24:61 */
// 0x080000000000000000000000000000000000
// /* "":17:170 */
// addmod
// /* "":10:280 */
// addmod
// /* "":6:281 */
// pop
// /* "":0:283 */
// stop

View File

@ -0,0 +1,105 @@
{
for { let i_0 := 0 } lt(i_0, 0x60) { i_0 := add(i_0, 0x20) }
{
switch mload(0x1fffffffffffffffffffffffff)
case 0x1ffffffffffffffffffffffffff { }
default {
for { let i_1 := 0 } lt(i_1, 0x60) { i_1 := add(i_1, 0x20) }
{
switch 0x1fffffffffffffffffffffffffff
case 0x1ffffffffffffffffffffffffffff { }
default { break }
continue
let x_4, x_5
}
}
let x_6, x_7
}
}
// ====
// stackOptimization: true
// ----
// /* "":23:24 */
// 0x00
// /* "":27:40 */
// tag_1:
// /* "":35:39 */
// 0x60
// /* "":27:40 */
// dup2
// lt
// tag_2
// jumpi
// /* "":6:527 */
// tag_3:
// stop
// /* "":71:527 */
// tag_2:
// /* "":59:63 */
// 0x20
// /* "":88:123 */
// swap1
// /* "":94:122 */
// 0x1fffffffffffffffffffffffff
// /* "":88:123 */
// mload
// /* "":137:166 */
// 0x01ffffffffffffffffffffffffff
// /* "":132:170 */
// eq
// tag_4
// jumpi
// /* "":81:500 */
// tag_5:
// /* "":218:219 */
// 0x00
// /* "":222:235 */
// tag_6:
// /* "":230:234 */
// 0x60
// /* "":222:235 */
// dup2
// lt
// tag_7
// jumpi
// /* "":201:490 */
// tag_8:
// /* "":187:500 */
// pop
// /* "":81:500 */
// tag_9:
// /* "":509:521 */
// 0x00
// dup1
// /* "":71:527 */
// pop
// pop
// /* "":50:64 */
// add
// /* "":41:66 */
// jump(tag_1)
// /* "":274:490 */
// tag_7:
// /* "":299:329 */
// 0x1fffffffffffffffffffffffffff
// /* "":353:384 */
// 0x01ffffffffffffffffffffffffffff
// /* "":346:388 */
// eq
// tag_10
// jumpi
// /* "":292:422 */
// tag_11:
// /* "":415:420 */
// jump(tag_8)
// /* "":385:388 */
// tag_10:
// dup3
// swap1
// /* "":245:259 */
// add
// /* "":236:261 */
// jump(tag_6)
// /* "":167:170 */
// tag_4:
// jump(tag_9)

View File

@ -0,0 +1,132 @@
{
for { let i_0 := 0 } lt(i_0, 0x60) { i_0 := add(i_0, 0x20) }
{
switch mload(0x1fffffffffffffffffffffffff)
case 0x1ffffffffffffffffffffffffff { }
default {
for { let i_1 := 0 } lt(i_1, 0x60) { i_1 := add(i_1, 0x20) }
{
switch 0x1fffffffffffffffffffffffffff
case 0x1ffffffffffffffffffffffffffff { }
default { break }
continue
let x_4, x_5
}
}
let x_6, x_7
}
}
// ====
// stackOptimization: false
// ----
// /* "":23:24 */
// 0x00
// /* "":6:527 */
// tag_1:
// /* "":35:39 */
// 0x60
// /* "":30:33 */
// dup2
// /* "":27:40 */
// lt
// /* "":6:527 */
// iszero
// tag_3
// jumpi
// /* "":94:122 */
// 0x1fffffffffffffffffffffffff
// /* "":88:123 */
// mload
// /* "":137:166 */
// 0x01ffffffffffffffffffffffffff
// /* "":132:170 */
// dup2
// eq
// tag_5
// jumpi
// /* "":218:219 */
// 0x00
// /* "":201:490 */
// tag_6:
// /* "":230:234 */
// 0x60
// /* "":225:228 */
// dup2
// /* "":222:235 */
// lt
// /* "":201:490 */
// iszero
// tag_8
// jumpi
// /* "":299:329 */
// 0x1fffffffffffffffffffffffffff
// /* "":353:384 */
// 0x01ffffffffffffffffffffffffffff
// /* "":346:388 */
// dup2
// eq
// tag_10
// jumpi
// /* "":415:420 */
// pop
// jump(tag_8)
// /* "":292:422 */
// jump(tag_9)
// /* "":346:388 */
// tag_10:
// /* "":292:422 */
// tag_9:
// pop
// /* "":439:447 */
// jump(tag_7)
// /* "":464:476 */
// 0x00
// 0x00
// /* "":274:490 */
// pop
// pop
// /* "":201:490 */
// tag_7:
// /* "":254:258 */
// 0x20
// /* "":249:252 */
// dup2
// /* "":245:259 */
// add
// /* "":238:259 */
// swap1
// pop
// /* "":201:490 */
// jump(tag_6)
// tag_8:
// /* "":205:221 */
// pop
// /* "":81:500 */
// jump(tag_4)
// /* "":132:170 */
// tag_5:
// /* "":81:500 */
// tag_4:
// pop
// /* "":509:521 */
// 0x00
// 0x00
// /* "":71:527 */
// pop
// pop
// /* "":6:527 */
// tag_2:
// /* "":59:63 */
// 0x20
// /* "":54:57 */
// dup2
// /* "":50:64 */
// add
// /* "":43:64 */
// swap1
// pop
// /* "":6:527 */
// jump(tag_1)
// tag_3:
// /* "":10:26 */
// pop

View File

@ -0,0 +1,105 @@
{
{
let i := 0
let i_1 := i
let _1 := mload(0x1fffffffffffffffffffffffff)
for { } true { i_1 := add(i_1, 0x20) }
{
let _2 := 0x60
if iszero(lt(i_1, _2)) { break }
switch _1
case 0x1ffffffffffffffffffffffffff { }
default {
let i_2 := i
if lt(i, _2) { }
}
}
}
}
// ----
// /* "":25:26 */
// 0x00
// /* "":46:47 */
// dup1
// /* "":72:100 */
// 0x1fffffffffffffffffffffffff
// /* "":66:101 */
// mload
// /* "":110:411 */
// tag_1:
// /* "":118:122 */
// 0x01
// /* "":110:411 */
// iszero
// tag_3
// jumpi
// /* "":181:185 */
// 0x60
// /* "":216:218 */
// dup1
// /* "":211:214 */
// dup4
// /* "":208:219 */
// lt
// /* "":201:220 */
// iszero
// /* "":198:230 */
// iszero
// tag_4
// jumpi
// /* "":223:228 */
// pop
// jump(tag_3)
// /* "":198:230 */
// tag_4:
// /* "":250:252 */
// dup2
// /* "":270:299 */
// 0x01ffffffffffffffffffffffffff
// /* "":265:303 */
// dup2
// eq
// tag_6
// jumpi
// /* "":353:354 */
// dup5
// /* "":380:382 */
// dup3
// /* "":377:378 */
// dup7
// /* "":374:383 */
// lt
// /* "":371:387 */
// iszero
// tag_7
// jumpi
// tag_7:
// /* "":324:401 */
// pop
// /* "":243:401 */
// jump(tag_5)
// /* "":265:303 */
// tag_6:
// /* "":243:401 */
// tag_5:
// pop
// /* "":157:411 */
// pop
// /* "":110:411 */
// tag_2:
// /* "":141:145 */
// 0x20
// /* "":136:139 */
// dup3
// /* "":132:146 */
// add
// /* "":125:146 */
// swap2
// pop
// /* "":110:411 */
// jump(tag_1)
// tag_3:
// /* "":6:417 */
// pop
// pop
// pop

View File

@ -0,0 +1,89 @@
{
{
let i := 0
let i_1 := i
let _1 := mload(0x1fffffffffffffffffffffffff)
for { } true { i_1 := add(i_1, 0x20) }
{
let _2 := 0x60
if iszero(lt(i_1, _2)) { break }
switch _1
case 0x1ffffffffffffffffffffffffff { }
default {
let i_2 := i
if lt(i, _2) { }
}
}
}
}
// ====
// stackOptimization: true
// ----
// /* "":25:26 */
// 0x00
// /* "":35:47 */
// dup1
// /* "":66:101 */
// swap1
// /* "":72:100 */
// 0x1fffffffffffffffffffffffff
// /* "":66:101 */
// mload
// /* "":114:117 */
// swap2
// /* "":157:411 */
// tag_1:
// /* "":181:185 */
// 0x60
// /* "":208:219 */
// swap1
// dup2
// dup2
// lt
// /* "":201:220 */
// iszero
// /* "":198:230 */
// tag_2
// jumpi
// /* "":157:411 */
// tag_3:
// /* "":141:145 */
// 0x20
// /* "":243:401 */
// swap2
// dup5
// /* "":270:299 */
// 0x01ffffffffffffffffffffffffff
// /* "":265:303 */
// eq
// tag_4
// jumpi
// /* "":243:401 */
// tag_5:
// /* "":342:354 */
// dup4
// /* "":374:383 */
// pop
// dup4
// lt
// /* "":371:387 */
// tag_6
// jumpi
// /* "":243:401 */
// tag_7:
// tag_8:
// /* "":132:146 */
// add
// /* "":123:148 */
// jump(tag_1)
// /* "":384:387 */
// tag_6:
// jump(tag_7)
// /* "":300:303 */
// tag_4:
// pop
// jump(tag_8)
// /* "":221:230 */
// tag_2:
// /* "":110:411 */
// stop