Skip to content

Commit

Permalink
Indexed stack shuffling.
Browse files Browse the repository at this point in the history
  • Loading branch information
ekpyron authored and r0qs committed Feb 19, 2024
1 parent f78eec5 commit 6821c91
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 69 deletions.
130 changes: 83 additions & 47 deletions libyul/backends/evm/StackHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <range/v3/view/iota.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/take.hpp>
#include <range/v3/view/transform.hpp>

namespace solidity::yul
{
Expand Down Expand Up @@ -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<FunctionCallReturnLabelSlot>(&_slot))
return m_functionCallReturnLabelSlotMultiplicity[*p];
return getIndex(m_functionCallReturnLabelSlotIndex, *p);
if (std::holds_alternative<FunctionReturnLabelSlot>(_slot))
return m_functionReturnLabelSlotMultiplicity;
{
m_indexedSlots[1] = _slot;
return 1;
}
if (auto* p = std::get_if<VariableSlot>(&_slot))
return m_variableSlotMultiplicity[*p];
return getIndex(m_variableSlotIndex, *p);
if (auto* p = std::get_if<LiteralSlot>(&_slot))
return m_literalSlotMultiplicity[*p];
return getIndex(m_literalSlotIndex, *p);
if (auto* p = std::get_if<TemporarySlot>(&_slot))
return m_temporarySlotMultiplicity[*p];
yulAssert(std::holds_alternative<JunkSlot>(_slot));
return m_junkSlotMultiplicity;
return getIndex(m_temporarySlotIndex, *p);
m_indexedSlots[0] = _slot;
return 0;
}

int at(StackSlot const& _slot) const
std::vector<StackSlot> indexedSlots()
{
if (auto* p = std::get_if<FunctionCallReturnLabelSlot>(&_slot))
return m_functionCallReturnLabelSlotMultiplicity.at(*p);
if (std::holds_alternative<FunctionReturnLabelSlot>(_slot))
return m_functionReturnLabelSlotMultiplicity;
if (auto* p = std::get_if<VariableSlot>(&_slot))
return m_variableSlotMultiplicity.at(*p);
if (auto* p = std::get_if<LiteralSlot>(&_slot))
return m_literalSlotMultiplicity.at(*p);
if (auto* p = std::get_if<TemporarySlot>(&_slot))
return m_temporarySlotMultiplicity.at(*p);
yulAssert(std::holds_alternative<JunkSlot>(_slot));
return m_junkSlotMultiplicity;
return std::move(m_indexedSlots);
}

private:
std::map<FunctionCallReturnLabelSlot, int> m_functionCallReturnLabelSlotMultiplicity;
int m_functionReturnLabelSlotMultiplicity = 0;
std::map<VariableSlot, int> m_variableSlotMultiplicity;
std::map<LiteralSlot, int> m_literalSlotMultiplicity;
std::map<TemporarySlot, int> m_temporarySlotMultiplicity;
int m_junkSlotMultiplicity = 0;
template<typename MapType, typename ElementType>
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<FunctionCallReturnLabelSlot, size_t> m_functionCallReturnLabelSlotIndex;
std::map<VariableSlot, size_t> m_variableSlotIndex;
std::map<LiteralSlot, size_t> m_literalSlotIndex;
std::map<TemporarySlot, size_t> m_temporarySlotIndex;
std::vector<StackSlot> m_indexedSlots{JunkSlot{}, JunkSlot{}};
};

/// Transforms @a _currentStack to @a _targetStack, invoking the provided shuffling operations.
Expand All @@ -432,31 +432,59 @@ class Multiplicity
template<typename Swap, typename PushOrDup, typename Pop>
void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop)
{
std::vector<StackSlot> indexedSlots;
using IndexedStack = std::vector<size_t>;
size_t junkIndex = 0;
IndexingMap indexer;
auto indexTransform = ranges::views::transform([&](auto const& _slot) { return indexer[_slot]; });
IndexedStack _targetStackIndexed = _targetStack | indexTransform | ranges::to<IndexedStack>;
IndexedStack _currentStackIndexed = _currentStack | indexTransform | ranges::to<IndexedStack>;
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<int> multiplicity;
size_t junkIndex = std::numeric_limits<size_t>::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<JunkSlot>(slot) && offset < currentStack.size())
if (slot == _junkIndex && offset < currentStack.size())
++multiplicity[currentStack.at(offset)];
else
++multiplicity[slot];
Expand All @@ -467,7 +495,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
_source < currentStack.size() &&
_target < targetStack.size() &&
(
std::holds_alternative<JunkSlot>(targetStack.at(_target)) ||
junkIndex == targetStack.at(_target) ||
currentStack.at(_source) == targetStack.at(_target)
);
}
Expand All @@ -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<JunkSlot>(targetStack.at(offset));
return offset < targetStack.size() && junkIndex == targetStack.at(offset);
}
void swap(size_t _i)
{
Expand All @@ -498,7 +526,15 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
}
};

Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop);
Shuffler<ShuffleOperations>::shuffle(
_currentStackIndexed,
_targetStackIndexed,
swapIndexed,
pushOrDupIndexed,
popIndexed,
indexedSlots.size(),
junkIndex
);

yulAssert(_currentStack.size() == _targetStack.size(), "");
for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack))
Expand Down
61 changes: 39 additions & 22 deletions libyul/backends/evm/StackLayoutGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ std::vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _s
template<typename Callable>
Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly)
{
std::vector<StackSlot> indexedSlots;
using IndexedStack = std::vector<size_t>;
size_t junkIndex = std::numeric_limits<size_t>::max();
IndexingMap indexer;
auto indexTransform = ranges::views::transform([&](auto const& _slot) { return indexer[_slot]; });
IndexedStack operationOutputIndexed = _operationOutput | indexTransform | ranges::to<IndexedStack>;
IndexedStack postIndexed = _post | indexTransform | ranges::to<IndexedStack>;
indexedSlots = indexer.indexedSlots();

struct PreviousSlot { size_t slot; };

// Determine the number of slots that have to be on stack before executing the operation (excluding
Expand All @@ -159,34 +168,42 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
// PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output<m>]
auto layout = ranges::views::iota(0u, preOperationLayoutSize) |
ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) |
ranges::to<std::vector<std::variant<PreviousSlot, StackSlot>>>;
layout += _operationOutput;
ranges::to<std::vector<std::variant<PreviousSlot, size_t>>>;
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<std::variant<PreviousSlot, StackSlot>>& layout;
Stack const& post;
std::set<StackSlot> outputs;
Multiplicity multiplicity;
Callable generateSlotOnTheFly;
std::vector<std::variant<PreviousSlot, size_t>>& layout;
IndexedStack const& post;
std::set<size_t> outputs;
std::vector<int> multiplicity;
decltype(generateSlotOnTheFlyIndexed) generateSlotOnTheFly;
size_t junkIndex = std::numeric_limits<size_t>::max();
ShuffleOperations(
std::vector<std::variant<PreviousSlot, StackSlot>>& _layout,
Stack const& _post,
Callable _generateSlotOnTheFly
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly)
std::vector<std::variant<PreviousSlot, size_t>>& _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<StackSlot>(&layoutSlot))
if (size_t const* slot = std::get_if<size_t>(&layoutSlot))
outputs.insert(*slot);

for (auto const& layoutSlot: layout)
if (StackSlot const* slot = std::get_if<StackSlot>(&layoutSlot))
if (size_t const* slot = std::get_if<size_t>(&layoutSlot))
--multiplicity[*slot];
for (auto&& slot: post)
if (outputs.count(slot) || generateSlotOnTheFly(slot))
Expand All @@ -198,28 +215,28 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
_source < layout.size() &&
_target < post.size() &&
(
std::holds_alternative<JunkSlot>(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))
);
}
bool sourceIsSame(size_t _lhs, size_t _rhs)
{
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));
}
int sourceMultiplicity(size_t _offset)
{
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)
Expand All @@ -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<JunkSlot>(post.at(_offset));
return _offset < post.size() && junkIndex == post.at(_offset);
}
void swap(size_t _i)
{
Expand All @@ -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<ShuffleOperations>::shuffle(layout, _post, _generateSlotOnTheFly);
Shuffler<ShuffleOperations>::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<std::optional<StackSlot>> idealLayout(_post.size(), std::nullopt);
for (auto&& [slot, idealPosition]: ranges::zip_view(_post, layout))
std::vector<std::optional<StackSlot>> idealLayout(postIndexed.size(), std::nullopt);
for (auto&& [slot, idealPosition]: ranges::zip_view(postIndexed, layout))
if (PreviousSlot* previousSlot = std::get_if<PreviousSlot>(&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())
Expand Down

0 comments on commit 6821c91

Please sign in to comment.