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/reverse.hpp>
#include <range/v3/view/take.hpp>
#include <range/v3/view/transform.hpp>
namespace solidity::yul
{
@ -377,49 +378,48 @@ private:
}
};
/// 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;
if (auto* p = std::get_if<VariableSlot>(&_slot))
return m_variableSlotMultiplicity[*p];
if (auto* p = std::get_if<LiteralSlot>(&_slot))
return m_literalSlotMultiplicity[*p];
if (auto* p = std::get_if<TemporarySlot>(&_slot))
return m_temporarySlotMultiplicity[*p];
yulAssert(std::holds_alternative<JunkSlot>(_slot));
return m_junkSlotMultiplicity;
}
int at(StackSlot const& _slot) const
{
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;
m_indexedSlots[1] = _slot;
return 1;
}
if (auto* p = std::get_if<VariableSlot>(&_slot))
return getIndex(m_variableSlotIndex, *p);
if (auto* p = std::get_if<LiteralSlot>(&_slot))
return getIndex(m_literalSlotIndex, *p);
if (auto* p = std::get_if<TemporarySlot>(&_slot))
return getIndex(m_temporarySlotIndex, *p);
m_indexedSlots[0] = _slot;
return 0;
}
std::vector<StackSlot> indexedSlots()
{
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.
@ -432,31 +432,59 @@ private:
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];
@ -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)
);
}
@ -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)
{
@ -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))

View File

@ -144,6 +144,15 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
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
@ -158,34 +167,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<vector<variant<PreviousSlot, StackSlot>>>;
layout += _operationOutput;
ranges::to<vector<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
{
vector<variant<PreviousSlot, StackSlot>>& layout;
Stack const& post;
std::set<StackSlot> outputs;
Multiplicity multiplicity;
Callable generateSlotOnTheFly;
vector<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(
vector<variant<PreviousSlot, StackSlot>>& _layout,
Stack const& _post,
Callable _generateSlotOnTheFly
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly)
vector<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 = get_if<StackSlot>(&layoutSlot))
if (size_t const* slot = get_if<size_t>(&layoutSlot))
outputs.insert(*slot);
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];
for (auto&& slot: post)
if (outputs.count(slot) || generateSlotOnTheFly(slot))
@ -197,12 +214,12 @@ 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))
);
}
@ -210,7 +227,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));
}
@ -218,7 +235,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)
@ -229,7 +246,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)
{
@ -241,17 +258,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.
vector<optional<StackSlot>> idealLayout(_post.size(), nullopt);
for (auto&& [slot, idealPosition]: ranges::zip_view(_post, layout))
vector<optional<StackSlot>> idealLayout(postIndexed.size(), 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())

View File

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