From 3ff457c31265cecdb8aadacb459ae8a543dd9b05 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Mon, 5 Dec 2022 14:24:32 +0100 Subject: [PATCH] Further customize max swap/dup. --- libevmasm/Assembly.cpp | 2 +- libevmasm/CommonSubexpressionEliminator.cpp | 12 +- libevmasm/CommonSubexpressionEliminator.h | 8 +- libsolidity/codegen/CompilerUtils.cpp | 12 +- libsolidity/codegen/LValue.cpp | 18 +-- libyul/backends/evm/EVMCodeTransform.cpp | 10 +- .../evm/OptimizedEVMCodeTransform.cpp | 4 +- libyul/backends/evm/StackHelpers.h | 26 ++-- libyul/backends/evm/StackLayoutGenerator.cpp | 56 ++++---- libyul/backends/evm/StackLayoutGenerator.h | 4 +- test/libevmasm/Optimiser.cpp | 8 +- test/libyul/StackShufflingTest.cpp | 2 +- test/libyul/evmCodeTransform/test.yul | 24 ++++ test/libyul/evmCodeTransform/test2.yul | 105 ++++++++++++++ test/libyul/evmCodeTransform/test3.yul | 132 ++++++++++++++++++ test/libyul/evmCodeTransform/test4.yul | 105 ++++++++++++++ test/libyul/evmCodeTransform/test5.yul | 89 ++++++++++++ 17 files changed, 549 insertions(+), 68 deletions(-) create mode 100644 test/libyul/evmCodeTransform/test.yul create mode 100644 test/libyul/evmCodeTransform/test2.yul create mode 100644 test/libyul/evmCodeTransform/test3.yul create mode 100644 test/libyul/evmCodeTransform/test4.yul create mode 100644 test/libyul/evmCodeTransform/test5.yul diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 65052f3ed..83904b6ee 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -433,7 +433,7 @@ map const& Assembly::optimiseInternal( while (iter != m_items.end()) { KnownState emptyState; - CommonSubexpressionEliminator eliminator{emptyState}; + CommonSubexpressionEliminator eliminator{emptyState, maxSwap(), maxDup()}; auto orig = iter; iter = eliminator.feedItems(iter, m_items.end(), usesMSize); bool shouldReplace = false; diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index 38e27f092..58721207d 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -62,7 +62,7 @@ vector CommonSubexpressionEliminator::getOptimizedItems() for (int height = minHeight; height <= m_state.stackHeight(); ++height) 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.stackHeight(), initialStackContents, @@ -125,9 +125,11 @@ void CommonSubexpressionEliminator::optimizeBreakingItem() CSECodeGenerator::CSECodeGenerator( ExpressionClasses& _expressionClasses, - vector const& _storeOperations + vector const& _storeOperations, + unsigned _maxSwap, + unsigned _maxDup ): - m_expressionClasses(_expressionClasses) + m_expressionClasses(_expressionClasses), m_maxSwap(_maxSwap), m_maxDup(_maxDup) { for (auto const& store: _storeOperations) 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, ""); int instructionNum = 1 + m_stackHeight - _fromPosition; - assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString); + assertThrow(instructionNum <= static_cast(m_maxDup), StackTooDeepException, util::stackTooDeepString); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); appendItem(AssemblyItem(AssemblyItemType::Dup, instructionNum, _location)); m_stack[m_stackHeight] = m_stack[_fromPosition]; @@ -485,7 +487,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons if (_fromPosition == m_stackHeight) return; int instructionNum = m_stackHeight - _fromPosition; - assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString); + assertThrow(instructionNum <= static_cast(m_maxSwap), StackTooDeepException, util::stackTooDeepString); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); appendItem(AssemblyItem(AssemblyItemType::Swap, instructionNum, _location)); diff --git a/libevmasm/CommonSubexpressionEliminator.h b/libevmasm/CommonSubexpressionEliminator.h index b0810a9a1..4b095a7d4 100644 --- a/libevmasm/CommonSubexpressionEliminator.h +++ b/libevmasm/CommonSubexpressionEliminator.h @@ -66,7 +66,7 @@ public: using Id = ExpressionClasses::Id; 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 /// 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. /// It is usually appended to the block but can be optimized in some cases. 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. /// 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 /// @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. std::set m_finalClasses; std::map m_targetStack; + unsigned m_maxSwap = 16; + unsigned m_maxDup = 16; }; template diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 4cab88169..df866452d 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -475,7 +475,7 @@ void CompilerUtils::encodeToMemory( m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; dynPointers++; assertThrow( - (argSize + dynPointers) < 16, + (argSize + dynPointers) < m_context.assembly().maxSwap(), StackTooDeepError, util::stackTooDeepString ); @@ -536,7 +536,7 @@ void CompilerUtils::encodeToMemory( { // copy tail pointer (=mem_end - mem_start) to memory assertThrow( - (2 + dynPointers) <= 16, + (2 + dynPointers) <= m_context.assembly().maxDup(), StackTooDeepError, util::stackTooDeepString ); @@ -1415,7 +1415,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) unsigned const size = _variable.annotation().type->sizeOnStack(); solAssert(stackPosition >= size, "Variable size and position mismatch."); // 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( StackTooDeepError() << errinfo_sourceLocation(_variable.location()) << @@ -1428,7 +1428,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) { assertThrow( - _stackDepth <= 16, + _stackDepth <= m_context.assembly().maxDup(), StackTooDeepError, util::stackTooDeepString ); @@ -1454,7 +1454,7 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::rotateStackUp(unsigned _items) { assertThrow( - _items - 1 <= 16, + _items - 1 <= m_context.assembly().maxSwap(), StackTooDeepError, util::stackTooDeepString ); @@ -1465,7 +1465,7 @@ void CompilerUtils::rotateStackUp(unsigned _items) void CompilerUtils::rotateStackDown(unsigned _items) { assertThrow( - _items - 1 <= 16, + _items - 1 <= m_context.assembly().maxSwap(), StackTooDeepError, util::stackTooDeepString ); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index faca484e7..0b1d77091 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -48,7 +48,7 @@ StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclarat void StackVariable::retrieveValue(SourceLocation const& _location, bool) const { 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( StackTooDeepError() << 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 { unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; - if (stackDiff > 16) - BOOST_THROW_EXCEPTION( - StackTooDeepError() << - errinfo_sourceLocation(_location) << - util::errinfo_comment(util::stackTooDeepString) - ); - else if (stackDiff > 0) + if (stackDiff > 0) + { + if (stackDiff > m_context.assembly().maxSwap()) + BOOST_THROW_EXCEPTION( + StackTooDeepError() << + errinfo_sourceLocation(_location) << + util::errinfo_comment(util::stackTooDeepString) + ); for (unsigned i = 0; i < m_size; ++i) m_context << AssemblyItem(AssemblyItemType::Swap, stackDiff) << Instruction::POP; + } if (!_move) retrieveValue(_location); } diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 2127c8962..a2e9565f0 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -180,7 +180,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) bool foundUnusedSlot = false; for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it) { - if (m_assembly.stackHeight() - *it > 17) + if (m_assembly.stackHeight() - *it > static_cast(m_assembly.maxSwap() + 1)) continue; foundUnusedSlot = true; auto slot = static_cast(*it); @@ -454,16 +454,16 @@ void CodeTransform::operator()(FunctionDefinition const& _function) &std::get(virtualFunctionScope->identifiers.at(returnVariable.name)) )) = static_cast(n); - if (stackLayout.size() > 17) + if (stackLayout.size() > m_assembly.maxSwap() + 1) { StackTooDeepError error( _function.name, YulString{}, - static_cast(stackLayout.size()) - 17, + static_cast(stackLayout.size()) - static_cast(m_assembly.maxSwap() + 1), "The function " + _function.name.str() + " 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." ); stackError(std::move(error), m_assembly.stackHeight() - static_cast(_function.parameters.size())); @@ -777,7 +777,7 @@ size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString yulAssert(m_context->variableStackHeights.count(&_var), ""); size_t heightDiff = static_cast(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var]; 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) { m_stackErrors.emplace_back( diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index e49f7987a..3da242c98 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -359,7 +359,9 @@ void OptimizedEVMCodeTransform::createStackLayout(std::shared_ptr(m_stack.size()), ""); } diff --git a/libyul/backends/evm/StackHelpers.h b/libyul/backends/evm/StackHelpers.h index ff8887b24..96d2d348e 100644 --- a/libyul/backends/evm/StackHelpers.h +++ b/libyul/backends/evm/StackHelpers.h @@ -127,10 +127,10 @@ private: static bool dupDeepSlotIfRequired(ShuffleOperations& _ops) { // 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; // 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. if (!_ops.isCompatible(sourceOffset, sourceOffset)) @@ -255,10 +255,10 @@ private: ) { // 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. - 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) { ops.swap(swapDepth); @@ -326,7 +326,7 @@ private: yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), ""); 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. for (size_t offset: swappableOffsets) @@ -386,7 +386,7 @@ private: /// its argument to the stack top. /// @a _pop is a function with signature void() that is called when the top most slot is popped. template -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 { @@ -396,18 +396,24 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw PushOrDup pushOrDupCallback; Pop popCallback; std::map multiplicity; + size_t const m_maxSwap = 16; + size_t const m_maxDup = 16; ShuffleOperations( Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, - Pop _pop + Pop _pop, + size_t _maxSwap, + size_t _maxDup ): currentStack(_currentStack), targetStack(_targetStack), swapCallback(_swap), pushOrDupCallback(_pushOrDup), - popCallback(_pop) + popCallback(_pop), + m_maxSwap(_maxSwap), + m_maxDup(_maxDup) { for (auto const& slot: currentStack) --multiplicity[slot]; @@ -417,6 +423,8 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw else ++multiplicity[slot]; } + size_t maxSwap() const { return m_maxSwap; } + size_t maxDup() const { return m_maxDup; } bool isCompatible(size_t _source, size_t _target) { return @@ -454,7 +462,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw } }; - Shuffler::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop); + Shuffler::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop, _maxSwap, _maxDup); yulAssert(_currentStack.size() == _targetStack.size(), ""); for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack)) diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index f8262d0c5..c3d8224bd 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -96,7 +96,7 @@ StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout): m_layout(_layo namespace { /// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target. -vector findStackTooDeep(Stack const& _source, Stack const& _target) +vector findStackTooDeep(Stack const& _source, Stack const& _target, size_t _maxSwap, size_t _maxDup) { Stack currentStack = _source; vector stackTooDeepErrors; @@ -113,9 +113,9 @@ vector findStackTooDeep(Stack const& _source _target, [&](unsigned _i) { - if (_i > 16) + if (_i > _maxSwap) stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ - _i - 16, + _i - _maxSwap, getVariableChoices(currentStack | ranges::views::take_last(_i + 1)) }); }, @@ -125,14 +125,16 @@ vector findStackTooDeep(Stack const& _source return; if ( auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot); - depth && *depth >= 16 + depth && *depth >= _maxDup ) stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ - *depth - 15, + *depth - (_maxDup - 1), getVariableChoices(currentStack | ranges::views::take_last(*depth + 1)) }); }, - [&]() {} + [&]() {}, + _maxSwap, + _maxDup ); return stackTooDeepErrors; } @@ -142,7 +144,7 @@ vector findStackTooDeep(Stack const& _source /// 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. template -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; }; @@ -174,11 +176,15 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla std::set outputs; std::map multiplicity; Callable generateSlotOnTheFly; + size_t m_maxSwap = 16; + size_t m_maxDup = 16; ShuffleOperations( vector>& _layout, Stack const& _post, - Callable _generateSlotOnTheFly - ): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly) + Callable _generateSlotOnTheFly, + size_t _maxSwap, + size_t _maxDup + ): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly), m_maxSwap(_maxSwap), m_maxDup(_maxDup) { for (auto const& layoutSlot: layout) if (StackSlot const* slot = get_if(&layoutSlot)) @@ -191,6 +197,8 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla if (outputs.count(slot) || generateSlotOnTheFly(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) { return @@ -241,7 +249,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla void pop() { layout.pop_back(); } void pushOrDupTarget(size_t _offset) { layout.push_back(post.at(_offset)); } }; - Shuffler::shuffle(layout, _post, _generateSlotOnTheFly); + Shuffler::shuffle(layout, _post, _generateSlotOnTheFly, _maxSwap, _maxDup); // 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 @@ -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 // 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. if (auto const* assignment = get_if(&_operation.operation)) @@ -304,7 +312,7 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG stack.pop_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(); else break; @@ -322,7 +330,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse) { 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. return propagateStackThroughBlock(std::move(_exitStack), _block, true); 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. // 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 { size_t numOps = 0; 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) { if (canBeFreelyGenerated(_slot)) return; auto depth = util::findOffset(ranges::concat_view(commonPrefix, testStack) | ranges::views::reverse, _slot); - if (depth && *depth >= 16) + if (depth && *depth >= maxDup()) numOps += 1000; }; - createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){}); + createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup()); testStack = _candidate; - createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){}); + createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup()); return numOps; }; @@ -642,7 +650,7 @@ vector StackLayoutGenerator::reportStackTooD { Stack& operationEntry = m_layout.operationEntryLayout.at(&operation); - stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry); + stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, maxSwap(), maxDup()); currentStack = operationEntry; for (size_t i = 0; i < operation.input.size(); i++) currentStack.pop_back(); @@ -656,7 +664,7 @@ vector StackLayoutGenerator::reportStackTooD [&](CFG::BasicBlock::Jump const& _jump) { Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout; - stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout); + stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup()); if (!_jump.backwards) _addChild(_jump.target); @@ -667,7 +675,7 @@ vector StackLayoutGenerator::reportStackTooD m_layout.blockInfos.at(_conditionalJump.zero).entryLayout, m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout }) - stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout); + stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup()); _addChild(_conditionalJump.zero); _addChild(_conditionalJump.nonZero); @@ -679,7 +687,7 @@ vector StackLayoutGenerator::reportStackTooD return stackTooDeepErrors; } -Stack StackLayoutGenerator::compressStack(Stack _stack) +Stack StackLayoutGenerator::compressStack(Stack _stack) const { optional firstDupOffset; do @@ -697,7 +705,7 @@ Stack StackLayoutGenerator::compressStack(Stack _stack) break; } 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; break; @@ -766,7 +774,7 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::Functi } }; 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; }; /// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index 6307449ad..56563c6d1 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -100,7 +100,7 @@ private: /// 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. - 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 /// without countermeasures. @@ -109,7 +109,7 @@ private: /// @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 /// 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. void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr); diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 6fc3c5a74..52cc7d202 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -72,7 +72,7 @@ namespace bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) { 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()); AssemblyItems output = cse.getOptimizedItems(); @@ -107,7 +107,7 @@ namespace while (iter != _input.end()) { KnownState emptyState; - CommonSubexpressionEliminator eliminator{emptyState}; + CommonSubexpressionEliminator eliminator{emptyState, 16u, 16u}; auto orig = iter; iter = eliminator.feedItems(iter, _input.end(), usesMSize); bool shouldReplace = false; @@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks) Instruction::ORIGIN }); - evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()}; + evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState(), 16u, 16u}; // Make sure CSE breaks after AssignImmutable. 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) { evmasm::KnownState state; - evmasm::CommonSubexpressionEliminator cse(state); + evmasm::CommonSubexpressionEliminator cse(state, 16u, 16u); AssemblyItems input{ Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1, Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1, diff --git a/test/libyul/StackShufflingTest.cpp b/test/libyul/StackShufflingTest.cpp index 2a2c21749..110143a69 100644 --- a/test/libyul/StackShufflingTest.cpp +++ b/test/libyul/StackShufflingTest.cpp @@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE(swap_cycle) FunctionReturnLabelSlot{function}, JunkSlot{}, JunkSlot{} }; // Used to hit a swapping cycle. - createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){}); + createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){}, 16u, 16u); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libyul/evmCodeTransform/test.yul b/test/libyul/evmCodeTransform/test.yul new file mode 100644 index 000000000..7aabfdf9d --- /dev/null +++ b/test/libyul/evmCodeTransform/test.yul @@ -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 diff --git a/test/libyul/evmCodeTransform/test2.yul b/test/libyul/evmCodeTransform/test2.yul new file mode 100644 index 000000000..f1246ba2d --- /dev/null +++ b/test/libyul/evmCodeTransform/test2.yul @@ -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) diff --git a/test/libyul/evmCodeTransform/test3.yul b/test/libyul/evmCodeTransform/test3.yul new file mode 100644 index 000000000..55055be60 --- /dev/null +++ b/test/libyul/evmCodeTransform/test3.yul @@ -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 diff --git a/test/libyul/evmCodeTransform/test4.yul b/test/libyul/evmCodeTransform/test4.yul new file mode 100644 index 000000000..9830f2d3f --- /dev/null +++ b/test/libyul/evmCodeTransform/test4.yul @@ -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 diff --git a/test/libyul/evmCodeTransform/test5.yul b/test/libyul/evmCodeTransform/test5.yul new file mode 100644 index 000000000..73ff330a5 --- /dev/null +++ b/test/libyul/evmCodeTransform/test5.yul @@ -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