Indexed stack shuffling.

This commit is contained in:
Daniel Kirchner 2023-05-03 11:37:56 +02:00 committed by r0qs
parent 4af4e5c78f
commit 6498e64eeb
No known key found for this signature in database
GPG Key ID: 61503DBA6667276C
3 changed files with 123 additions and 70 deletions

View File

@ -29,6 +29,7 @@
#include <range/v3/view/iota.hpp> #include <range/v3/view/iota.hpp>
#include <range/v3/view/reverse.hpp> #include <range/v3/view/reverse.hpp>
#include <range/v3/view/take.hpp> #include <range/v3/view/take.hpp>
#include <range/v3/view/transform.hpp>
namespace solidity::yul namespace solidity::yul
{ {
@ -377,49 +378,48 @@ private:
} }
}; };
/// A simple optimized map for mapping StackSlots to ints. class IndexingMap
class Multiplicity
{ {
public: public:
int& operator[](StackSlot const& _slot) size_t operator[](StackSlot const& _slot)
{ {
if (auto* p = std::get_if<FunctionCallReturnLabelSlot>(&_slot)) if (auto* p = std::get_if<FunctionCallReturnLabelSlot>(&_slot))
return m_functionCallReturnLabelSlotMultiplicity[*p]; return getIndex(m_functionCallReturnLabelSlotIndex, *p);
if (std::holds_alternative<FunctionReturnLabelSlot>(_slot)) if (std::holds_alternative<FunctionReturnLabelSlot>(_slot))
return m_functionReturnLabelSlotMultiplicity; {
m_indexedSlots[1] = _slot;
return 1;
}
if (auto* p = std::get_if<VariableSlot>(&_slot)) 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)) 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)) if (auto* p = std::get_if<TemporarySlot>(&_slot))
return m_temporarySlotMultiplicity[*p]; return getIndex(m_temporarySlotIndex, *p);
yulAssert(std::holds_alternative<JunkSlot>(_slot)); m_indexedSlots[0] = _slot;
return m_junkSlotMultiplicity; return 0;
} }
std::vector<StackSlot> indexedSlots()
int at(StackSlot const& _slot) const
{ {
if (auto* p = std::get_if<FunctionCallReturnLabelSlot>(&_slot)) return std::move(m_indexedSlots);
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;
} }
private: private:
std::map<FunctionCallReturnLabelSlot, int> m_functionCallReturnLabelSlotMultiplicity; template<typename MapType, typename ElementType>
int m_functionReturnLabelSlotMultiplicity = 0; size_t getIndex(MapType&& _map, ElementType&& _element)
std::map<VariableSlot, int> m_variableSlotMultiplicity; {
std::map<LiteralSlot, int> m_literalSlotMultiplicity; auto [element, newlyInserted] = _map.emplace(std::make_pair(_element, size_t(0u)));
std::map<TemporarySlot, int> m_temporarySlotMultiplicity; if (newlyInserted)
int m_junkSlotMultiplicity = 0; {
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. /// Transforms @a _currentStack to @a _targetStack, invoking the provided shuffling operations.
@ -432,31 +432,59 @@ private:
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)
{ {
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 struct ShuffleOperations
{ {
Stack& currentStack; IndexedStack& currentStack;
Stack const& targetStack; IndexedStack const& targetStack;
Swap swapCallback; decltype(swapIndexed) swapCallback;
PushOrDup pushOrDupCallback; decltype(pushOrDupIndexed) pushOrDupCallback;
Pop popCallback; decltype(popIndexed) popCallback;
Multiplicity multiplicity; std::vector<int> multiplicity;
size_t junkIndex = std::numeric_limits<size_t>::max();
ShuffleOperations( ShuffleOperations(
Stack& _currentStack, IndexedStack& _currentStack,
Stack const& _targetStack, IndexedStack const& _targetStack,
Swap _swap, decltype(swapIndexed) _swap,
PushOrDup _pushOrDup, decltype(pushOrDupIndexed) _pushOrDup,
Pop _pop decltype(popIndexed) _pop,
size_t _numSlots,
size_t _junkIndex
): ):
currentStack(_currentStack), currentStack(_currentStack),
targetStack(_targetStack), targetStack(_targetStack),
swapCallback(_swap), swapCallback(_swap),
pushOrDupCallback(_pushOrDup), pushOrDupCallback(_pushOrDup),
popCallback(_pop) popCallback(_pop),
junkIndex(_junkIndex)
{ {
multiplicity.resize(_numSlots, 0);
for (auto const& slot: currentStack) for (auto const& slot: currentStack)
--multiplicity[slot]; --multiplicity[slot];
for (auto&& [offset, slot]: targetStack | ranges::views::enumerate) 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)]; ++multiplicity[currentStack.at(offset)];
else else
++multiplicity[slot]; ++multiplicity[slot];
@ -467,7 +495,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
_source < currentStack.size() && _source < currentStack.size() &&
_target < targetStack.size() && _target < targetStack.size() &&
( (
std::holds_alternative<JunkSlot>(targetStack.at(_target)) || junkIndex == targetStack.at(_target) ||
currentStack.at(_source) == 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)); } int targetMultiplicity(size_t _offset) { return multiplicity.at(targetStack.at(_offset)); }
bool targetIsArbitrary(size_t 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) void swap(size_t _i)
{ {
@ -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(), ""); 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

@ -144,6 +144,15 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
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)
{ {
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; }; struct PreviousSlot { size_t slot; };
// Determine the number of slots that have to be on stack before executing the operation (excluding // Determine the number of slots that have to be on stack before executing the operation (excluding
@ -158,34 +167,42 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
// PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output<m>] // PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output<m>]
auto layout = ranges::views::iota(0u, preOperationLayoutSize) | auto layout = ranges::views::iota(0u, preOperationLayoutSize) |
ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) | ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) |
ranges::to<vector<variant<PreviousSlot, StackSlot>>>; ranges::to<vector<variant<PreviousSlot, size_t>>>;
layout += _operationOutput; layout += operationOutputIndexed;
// Shortcut for trivial case. // Shortcut for trivial case.
if (layout.empty()) if (layout.empty())
return Stack{}; 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 // Next we will shuffle the layout to the post stack using ShuffleOperations
// that are aware of PreviousSlot's. // that are aware of PreviousSlot's.
struct ShuffleOperations struct ShuffleOperations
{ {
vector<variant<PreviousSlot, StackSlot>>& layout; vector<variant<PreviousSlot, size_t>>& layout;
Stack const& post; IndexedStack const& post;
std::set<StackSlot> outputs; std::set<size_t> outputs;
Multiplicity multiplicity; std::vector<int> multiplicity;
Callable generateSlotOnTheFly; decltype(generateSlotOnTheFlyIndexed) generateSlotOnTheFly;
size_t junkIndex = std::numeric_limits<size_t>::max();
ShuffleOperations( ShuffleOperations(
vector<variant<PreviousSlot, StackSlot>>& _layout, vector<variant<PreviousSlot, size_t>>& _layout,
Stack const& _post, IndexedStack const& _post,
Callable _generateSlotOnTheFly decltype(generateSlotOnTheFlyIndexed) _generateSlotOnTheFly,
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly) size_t _numSlots,
size_t _junkIndex
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly), junkIndex(_junkIndex)
{ {
multiplicity.resize(_numSlots, 0);
for (auto const& layoutSlot: layout) for (auto const& layoutSlot: layout)
if (StackSlot const* slot = get_if<StackSlot>(&layoutSlot)) if (size_t const* slot = get_if<size_t>(&layoutSlot))
outputs.insert(*slot); outputs.insert(*slot);
for (auto const& layoutSlot: layout) for (auto const& layoutSlot: layout)
if (StackSlot const* slot = get_if<StackSlot>(&layoutSlot)) if (size_t const* slot = get_if<size_t>(&layoutSlot))
--multiplicity[*slot]; --multiplicity[*slot];
for (auto&& slot: post) for (auto&& slot: post)
if (outputs.count(slot) || generateSlotOnTheFly(slot)) if (outputs.count(slot) || generateSlotOnTheFly(slot))
@ -197,12 +214,12 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
_source < layout.size() && _source < layout.size() &&
_target < post.size() && _target < post.size() &&
( (
std::holds_alternative<JunkSlot>(post.at(_target)) || junkIndex == post.at(_target) ||
std::visit(util::GenericVisitor{ std::visit(util::GenericVisitor{
[&](PreviousSlot const&) { [&](PreviousSlot const&) {
return !outputs.count(post.at(_target)) && !generateSlotOnTheFly(post.at(_target)); 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)) }, layout.at(_source))
); );
} }
@ -210,7 +227,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
{ {
return std::visit(util::GenericVisitor{ return std::visit(util::GenericVisitor{
[&](PreviousSlot const&, PreviousSlot const&) { return true; }, [&](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; } [&](auto const&, auto const&) { return false; }
}, layout.at(_lhs), layout.at(_rhs)); }, layout.at(_lhs), layout.at(_rhs));
} }
@ -218,7 +235,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
{ {
return std::visit(util::GenericVisitor{ return std::visit(util::GenericVisitor{
[&](PreviousSlot const&) { return 0; }, [&](PreviousSlot const&) { return 0; },
[&](StackSlot const& _s) { return multiplicity.at(_s); } [&](size_t _s) { return multiplicity.at(_s); }
}, layout.at(_offset)); }, layout.at(_offset));
} }
int targetMultiplicity(size_t _offset) int targetMultiplicity(size_t _offset)
@ -229,7 +246,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
} }
bool targetIsArbitrary(size_t _offset) 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) void swap(size_t _i)
{ {
@ -241,17 +258,17 @@ 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, postIndexed, generateSlotOnTheFlyIndexed, indexedSlots.size(), junkIndex);
// 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
// output in place. The resulting permutation of the PreviousSlot yields the ideal positions of slots // 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"}, // 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. // then we want the variable tmp in the slot at offset 2 in the layout before the operation.
vector<optional<StackSlot>> idealLayout(_post.size(), nullopt); vector<optional<StackSlot>> idealLayout(postIndexed.size(), nullopt);
for (auto&& [slot, idealPosition]: ranges::zip_view(_post, layout)) for (auto&& [slot, idealPosition]: ranges::zip_view(postIndexed, layout))
if (PreviousSlot* previousSlot = std::get_if<PreviousSlot>(&idealPosition)) 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. // 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()) while (!idealLayout.empty() && !idealLayout.back())

View File

@ -460,6 +460,7 @@ void CommandLineParser::parseOutputSelection()
); );
static set<string> const assemblerModeOutputs = { static set<string> const assemblerModeOutputs = {
CompilerOutputs::componentName(&CompilerOutputs::asm_), CompilerOutputs::componentName(&CompilerOutputs::asm_),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
CompilerOutputs::componentName(&CompilerOutputs::binary), CompilerOutputs::componentName(&CompilerOutputs::binary),
CompilerOutputs::componentName(&CompilerOutputs::irOptimized) CompilerOutputs::componentName(&CompilerOutputs::irOptimized)
}; };
@ -1178,7 +1179,6 @@ void CommandLineParser::processArgs()
// TODO: The list is not complete. Add more. // TODO: The list is not complete. Add more.
g_strOutputDir, g_strOutputDir,
g_strGas, g_strGas,
g_strCombinedJson,
g_strOptimizeYul, g_strOptimizeYul,
g_strNoOptimizeYul, g_strNoOptimizeYul,
}; };