diff --git a/libyul/backends/evm/StackHelpers.h b/libyul/backends/evm/StackHelpers.h index dae4352a02c4..5e46adf9902c 100644 --- a/libyul/backends/evm/StackHelpers.h +++ b/libyul/backends/evm/StackHelpers.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace solidity::yul { @@ -377,49 +378,48 @@ class Shuffler } }; -/// A simple optimized map for mapping StackSlots to ints. -class Multiplicity +class IndexingMap { public: - int& operator[](StackSlot const& _slot) + size_t operator[](StackSlot const& _slot) { if (auto* p = std::get_if(&_slot)) - return m_functionCallReturnLabelSlotMultiplicity[*p]; + return getIndex(m_functionCallReturnLabelSlotIndex, *p); if (std::holds_alternative(_slot)) - return m_functionReturnLabelSlotMultiplicity; + { + m_indexedSlots[1] = _slot; + return 1; + } if (auto* p = std::get_if(&_slot)) - return m_variableSlotMultiplicity[*p]; + return getIndex(m_variableSlotIndex, *p); if (auto* p = std::get_if(&_slot)) - return m_literalSlotMultiplicity[*p]; + return getIndex(m_literalSlotIndex, *p); if (auto* p = std::get_if(&_slot)) - return m_temporarySlotMultiplicity[*p]; - yulAssert(std::holds_alternative(_slot)); - return m_junkSlotMultiplicity; + return getIndex(m_temporarySlotIndex, *p); + m_indexedSlots[0] = _slot; + return 0; } - - int at(StackSlot const& _slot) const + std::vector indexedSlots() { - if (auto* p = std::get_if(&_slot)) - return m_functionCallReturnLabelSlotMultiplicity.at(*p); - if (std::holds_alternative(_slot)) - return m_functionReturnLabelSlotMultiplicity; - if (auto* p = std::get_if(&_slot)) - return m_variableSlotMultiplicity.at(*p); - if (auto* p = std::get_if(&_slot)) - return m_literalSlotMultiplicity.at(*p); - if (auto* p = std::get_if(&_slot)) - return m_temporarySlotMultiplicity.at(*p); - yulAssert(std::holds_alternative(_slot)); - return m_junkSlotMultiplicity; + return std::move(m_indexedSlots); } - private: - std::map m_functionCallReturnLabelSlotMultiplicity; - int m_functionReturnLabelSlotMultiplicity = 0; - std::map m_variableSlotMultiplicity; - std::map m_literalSlotMultiplicity; - std::map m_temporarySlotMultiplicity; - int m_junkSlotMultiplicity = 0; + template + size_t getIndex(MapType&& _map, ElementType&& _element) + { + auto [element, newlyInserted] = _map.emplace(std::make_pair(_element, size_t(0u))); + if (newlyInserted) + { + element->second = m_indexedSlots.size(); + m_indexedSlots.emplace_back(_element); + } + return element->second; + } + std::map m_functionCallReturnLabelSlotIndex; + std::map m_variableSlotIndex; + std::map m_literalSlotIndex; + std::map m_temporarySlotIndex; + std::vector m_indexedSlots{JunkSlot{}, JunkSlot{}}; }; /// Transforms @a _currentStack to @a _targetStack, invoking the provided shuffling operations. @@ -432,31 +432,59 @@ class Multiplicity template void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop) { + std::vector indexedSlots; + using IndexedStack = std::vector; + size_t junkIndex = 0; + IndexingMap indexer; + auto indexTransform = ranges::views::transform([&](auto const& _slot) { return indexer[_slot]; }); + IndexedStack _targetStackIndexed = _targetStack | indexTransform | ranges::to; + IndexedStack _currentStackIndexed = _currentStack | indexTransform | ranges::to; + indexedSlots = indexer.indexedSlots(); + + auto swapIndexed = [&](unsigned _index) { + _swap(_index); + std::swap(_currentStack.at(_currentStack.size() - _index - 1), _currentStack.back()); + }; + auto pushOrDupIndexed = [&](size_t _index) { + _pushOrDup(indexedSlots.at(_index)); + _currentStack.push_back(indexedSlots.at(_index)); + }; + auto popIndexed = [&]() { + _pop(); + _currentStack.pop_back(); + }; + + struct ShuffleOperations { - Stack& currentStack; - Stack const& targetStack; - Swap swapCallback; - PushOrDup pushOrDupCallback; - Pop popCallback; - Multiplicity multiplicity; + IndexedStack& currentStack; + IndexedStack const& targetStack; + decltype(swapIndexed) swapCallback; + decltype(pushOrDupIndexed) pushOrDupCallback; + decltype(popIndexed) popCallback; + std::vector multiplicity; + size_t junkIndex = std::numeric_limits::max(); ShuffleOperations( - Stack& _currentStack, - Stack const& _targetStack, - Swap _swap, - PushOrDup _pushOrDup, - Pop _pop + IndexedStack& _currentStack, + IndexedStack const& _targetStack, + decltype(swapIndexed) _swap, + decltype(pushOrDupIndexed) _pushOrDup, + decltype(popIndexed) _pop, + size_t _numSlots, + size_t _junkIndex ): currentStack(_currentStack), targetStack(_targetStack), swapCallback(_swap), pushOrDupCallback(_pushOrDup), - popCallback(_pop) + popCallback(_pop), + junkIndex(_junkIndex) { + multiplicity.resize(_numSlots, 0); for (auto const& slot: currentStack) --multiplicity[slot]; for (auto&& [offset, slot]: targetStack | ranges::views::enumerate) - if (std::holds_alternative(slot) && offset < currentStack.size()) + if (slot == _junkIndex && offset < currentStack.size()) ++multiplicity[currentStack.at(offset)]; else ++multiplicity[slot]; @@ -467,7 +495,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw _source < currentStack.size() && _target < targetStack.size() && ( - std::holds_alternative(targetStack.at(_target)) || + junkIndex == targetStack.at(_target) || currentStack.at(_source) == targetStack.at(_target) ); } @@ -476,7 +504,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw int targetMultiplicity(size_t _offset) { return multiplicity.at(targetStack.at(_offset)); } bool targetIsArbitrary(size_t offset) { - return offset < targetStack.size() && std::holds_alternative(targetStack.at(offset)); + return offset < targetStack.size() && junkIndex == targetStack.at(offset); } void swap(size_t _i) { @@ -498,7 +526,15 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw } }; - Shuffler::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop); + Shuffler::shuffle( + _currentStackIndexed, + _targetStackIndexed, + swapIndexed, + pushOrDupIndexed, + popIndexed, + indexedSlots.size(), + junkIndex + ); 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 88190b45b3fc..03c68ed6a062 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -145,6 +145,15 @@ std::vector findStackTooDeep(Stack const& _s template Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly) { + std::vector indexedSlots; + using IndexedStack = std::vector; + size_t junkIndex = std::numeric_limits::max(); + IndexingMap indexer; + auto indexTransform = ranges::views::transform([&](auto const& _slot) { return indexer[_slot]; }); + IndexedStack operationOutputIndexed = _operationOutput | indexTransform | ranges::to; + IndexedStack postIndexed = _post | indexTransform | ranges::to; + indexedSlots = indexer.indexedSlots(); + struct PreviousSlot { size_t slot; }; // Determine the number of slots that have to be on stack before executing the operation (excluding @@ -159,34 +168,42 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla // PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output] auto layout = ranges::views::iota(0u, preOperationLayoutSize) | ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) | - ranges::to>>; - layout += _operationOutput; + ranges::to>>; + layout += operationOutputIndexed; // Shortcut for trivial case. if (layout.empty()) return Stack{}; + auto generateSlotOnTheFlyIndexed = [&](size_t _slot) -> bool { + return _generateSlotOnTheFly(indexedSlots.at(_slot)); + }; + // Next we will shuffle the layout to the post stack using ShuffleOperations // that are aware of PreviousSlot's. struct ShuffleOperations { - std::vector>& layout; - Stack const& post; - std::set outputs; - Multiplicity multiplicity; - Callable generateSlotOnTheFly; + std::vector>& layout; + IndexedStack const& post; + std::set outputs; + std::vector multiplicity; + decltype(generateSlotOnTheFlyIndexed) generateSlotOnTheFly; + size_t junkIndex = std::numeric_limits::max(); ShuffleOperations( - std::vector>& _layout, - Stack const& _post, - Callable _generateSlotOnTheFly - ): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly) + std::vector>& _layout, + IndexedStack const& _post, + decltype(generateSlotOnTheFlyIndexed) _generateSlotOnTheFly, + size_t _numSlots, + size_t _junkIndex + ): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly), junkIndex(_junkIndex) { + multiplicity.resize(_numSlots, 0); for (auto const& layoutSlot: layout) - if (StackSlot const* slot = std::get_if(&layoutSlot)) + if (size_t const* slot = std::get_if(&layoutSlot)) outputs.insert(*slot); for (auto const& layoutSlot: layout) - if (StackSlot const* slot = std::get_if(&layoutSlot)) + if (size_t const* slot = std::get_if(&layoutSlot)) --multiplicity[*slot]; for (auto&& slot: post) if (outputs.count(slot) || generateSlotOnTheFly(slot)) @@ -198,12 +215,12 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla _source < layout.size() && _target < post.size() && ( - std::holds_alternative(post.at(_target)) || + junkIndex == post.at(_target) || std::visit(util::GenericVisitor{ [&](PreviousSlot const&) { return !outputs.count(post.at(_target)) && !generateSlotOnTheFly(post.at(_target)); }, - [&](StackSlot const& _s) { return _s == post.at(_target); } + [&](size_t const& _s) { return _s == post.at(_target); } }, layout.at(_source)) ); } @@ -211,7 +228,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla { return std::visit(util::GenericVisitor{ [&](PreviousSlot const&, PreviousSlot const&) { return true; }, - [&](StackSlot const& _lhs, StackSlot const& _rhs) { return _lhs == _rhs; }, + [&](size_t const& _lhs, size_t const& _rhs) { return _lhs == _rhs; }, [&](auto const&, auto const&) { return false; } }, layout.at(_lhs), layout.at(_rhs)); } @@ -219,7 +236,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla { return std::visit(util::GenericVisitor{ [&](PreviousSlot const&) { return 0; }, - [&](StackSlot const& _s) { return multiplicity.at(_s); } + [&](size_t _s) { return multiplicity.at(_s); } }, layout.at(_offset)); } int targetMultiplicity(size_t _offset) @@ -230,7 +247,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla } bool targetIsArbitrary(size_t _offset) { - return _offset < post.size() && std::holds_alternative(post.at(_offset)); + return _offset < post.size() && junkIndex == post.at(_offset); } void swap(size_t _i) { @@ -242,17 +259,17 @@ 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, postIndexed, generateSlotOnTheFlyIndexed, indexedSlots.size(), junkIndex); // 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 // output in place. The resulting permutation of the PreviousSlot yields the ideal positions of slots // before the operation, i.e. if PreviousSlot{2} is at a position at which _post contains VariableSlot{"tmp"}, // then we want the variable tmp in the slot at offset 2 in the layout before the operation. - std::vector> idealLayout(_post.size(), std::nullopt); - for (auto&& [slot, idealPosition]: ranges::zip_view(_post, layout)) + std::vector> idealLayout(postIndexed.size(), std::nullopt); + for (auto&& [slot, idealPosition]: ranges::zip_view(postIndexed, layout)) if (PreviousSlot* previousSlot = std::get_if(&idealPosition)) - idealLayout.at(previousSlot->slot) = slot; + idealLayout.at(previousSlot->slot) = indexedSlots.at(slot); // The tail of layout must have contained the operation outputs and will not have been assigned slots in the last loop. while (!idealLayout.empty() && !idealLayout.back())