mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Avoid dupping slots if doing so causes other needed slots to become unreachable.
This commit is contained in:
parent
74edc40a7e
commit
535d30bbb3
@ -24,6 +24,7 @@
|
|||||||
#include <libsolutil/Visitor.h>
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
#include <range/v3/algorithm/all_of.hpp>
|
#include <range/v3/algorithm/all_of.hpp>
|
||||||
|
#include <range/v3/algorithm/any_of.hpp>
|
||||||
#include <range/v3/view/enumerate.hpp>
|
#include <range/v3/view/enumerate.hpp>
|
||||||
#include <range/v3/view/iota.hpp>
|
#include <range/v3/view/iota.hpp>
|
||||||
#include <range/v3/view/reverse.hpp>
|
#include <range/v3/view/reverse.hpp>
|
||||||
@ -61,7 +62,7 @@ concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceO
|
|||||||
// Returns true, iff the current slot at sourceOffset in source layout is a suitable slot at targetOffset.
|
// Returns true, iff the current slot at sourceOffset in source layout is a suitable slot at targetOffset.
|
||||||
{ ops.isCompatible(sourceOffset, targetOffset) } -> std::convertible_to<bool>;
|
{ ops.isCompatible(sourceOffset, targetOffset) } -> std::convertible_to<bool>;
|
||||||
// Returns true, iff the slots at the two given source offsets are identical.
|
// Returns true, iff the slots at the two given source offsets are identical.
|
||||||
{ ops.souceIsSame(sourceOffset, sourceOffset) } -> std::convertible_to<bool>;
|
{ ops.sourceIsSame(sourceOffset, sourceOffset) } -> std::convertible_to<bool>;
|
||||||
// Returns a positive integer n, if the slot at the given source offset needs n more copies.
|
// Returns a positive integer n, if the slot at the given source offset needs n more copies.
|
||||||
// Returns a negative integer -n, if the slot at the given source offsets occurs n times too many.
|
// Returns a negative integer -n, if the slot at the given source offsets occurs n times too many.
|
||||||
// Returns zero if the amount of occurrences, in the current source layout, of the slot at the given source offset matches the desired amount of occurrences in the target.
|
// Returns zero if the amount of occurrences, in the current source layout, of the slot at the given source offset matches the desired amount of occurrences in the target.
|
||||||
@ -92,9 +93,8 @@ class Shuffler
|
|||||||
public:
|
public:
|
||||||
/// Executes the stack shuffling operations. Instantiates an instance of ShuffleOperations
|
/// Executes the stack shuffling operations. Instantiates an instance of ShuffleOperations
|
||||||
/// in each iteration. Each iteration performs exactly one operation that modifies the stack.
|
/// in each iteration. Each iteration performs exactly one operation that modifies the stack.
|
||||||
/// After shuffle, all slots in the source layout are guaranteed to be compatible to the slots
|
/// After shuffle, source and target have the same size and all slots in the source layout are
|
||||||
/// at the same target offset, but there may be additional slots in the target that are not
|
/// compatible to the slots at the same target offset.
|
||||||
/// pushed/dupped yet.
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
static void shuffle(Args&&... args)
|
static void shuffle(Args&&... args)
|
||||||
{
|
{
|
||||||
@ -107,19 +107,85 @@ public:
|
|||||||
yulAssert(!needsMoreShuffling, "Could not create stack layout after 1000 iterations.");
|
yulAssert(!needsMoreShuffling, "Could not create stack layout after 1000 iterations.");
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
// If dupping an ideal slot causes a slot that will still be required to become unreachable, then dup
|
||||||
|
// the latter slot first.
|
||||||
|
static bool pushDeepSlotIfRequired(ShuffleOperations& _ops)
|
||||||
|
{
|
||||||
|
// Check if the stack is large enough for anything to potentially become unreachable.
|
||||||
|
if (_ops.sourceSize() < 15)
|
||||||
|
return false;
|
||||||
|
// Check whether any deep slot might still be needed later.
|
||||||
|
for (size_t sourceOffset: ranges::views::iota(0u, std::min(_ops.sourceSize() - 15, _ops.targetSize())))
|
||||||
|
{
|
||||||
|
// We need another copy of this slot.
|
||||||
|
if (_ops.sourceMultiplicity(sourceOffset) > 0)
|
||||||
|
{
|
||||||
|
// If this slot occurs again later, we skip this occurrence.
|
||||||
|
if (ranges::any_of(
|
||||||
|
ranges::views::iota(sourceOffset + 1, _ops.sourceSize()),
|
||||||
|
[&](size_t _offset) { return _ops.sourceIsSame(sourceOffset, _offset); }
|
||||||
|
))
|
||||||
|
continue;
|
||||||
|
// Bring up the target slot that would otherwise become unreachable.
|
||||||
|
for (size_t targetOffset: ranges::views::iota(0u, _ops.targetSize()))
|
||||||
|
if (!_ops.targetIsArbitrary(targetOffset) && _ops.isCompatible(sourceOffset, targetOffset))
|
||||||
|
{
|
||||||
|
_ops.pushOrDupTarget(targetOffset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static void bringUpTargetSlot(ShuffleOperations& _ops, size_t _targetOffset)
|
||||||
|
{
|
||||||
|
if (pushDeepSlotIfRequired(_ops))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::list<size_t> toVisit{_targetOffset};
|
||||||
|
std::set<size_t> visited;
|
||||||
|
|
||||||
|
while (!toVisit.empty())
|
||||||
|
{
|
||||||
|
auto offset = *toVisit.begin();
|
||||||
|
toVisit.erase(toVisit.begin());
|
||||||
|
visited.emplace(offset);
|
||||||
|
if (_ops.targetMultiplicity(offset) > 0)
|
||||||
|
{
|
||||||
|
_ops.pushOrDupTarget(offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The desired target slot must already be somewhere else on stack right now.
|
||||||
|
for (auto nextOffset: ranges::views::iota(0u, std::min(_ops.sourceSize(), _ops.targetSize())))
|
||||||
|
if (
|
||||||
|
!_ops.isCompatible(nextOffset, nextOffset) &&
|
||||||
|
_ops.isCompatible(nextOffset, offset)
|
||||||
|
)
|
||||||
|
if (!visited.count(nextOffset))
|
||||||
|
toVisit.emplace_back(nextOffset);
|
||||||
|
}
|
||||||
|
yulAssert(false, "");
|
||||||
|
}
|
||||||
/// Performs a single stack operation, transforming the source layout closer to the target layout.
|
/// Performs a single stack operation, transforming the source layout closer to the target layout.
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
static bool shuffleStep(Args&&... args)
|
static bool shuffleStep(Args&&... args)
|
||||||
{
|
{
|
||||||
ShuffleOperations ops{std::forward<Args>(args)...};
|
ShuffleOperations ops{std::forward<Args>(args)...};
|
||||||
|
|
||||||
// Terminates, if all slots in the source are compatible with the target.
|
// All source slots are final.
|
||||||
// Note that there may still be more slots in the target.
|
|
||||||
if (ranges::all_of(
|
if (ranges::all_of(
|
||||||
ranges::views::iota(0u, ops.sourceSize()),
|
ranges::views::iota(0u, ops.sourceSize()),
|
||||||
[&](size_t _index) { return ops.isCompatible(_index, _index); }
|
[&](size_t _index) { return ops.isCompatible(_index, _index); }
|
||||||
))
|
))
|
||||||
|
{
|
||||||
|
// Bring up all remaining target slots, if any, or terminate otherwise.
|
||||||
|
if (ops.sourceSize() < ops.targetSize())
|
||||||
|
{
|
||||||
|
bringUpTargetSlot(ops, ops.sourceSize());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t sourceTop = ops.sourceSize() - 1;
|
size_t sourceTop = ops.sourceSize() - 1;
|
||||||
// If we no longer need the current stack top, we pop it, unless we need an arbitrary slot at this position
|
// If we no longer need the current stack top, we pop it, unless we need an arbitrary slot at this position
|
||||||
@ -149,32 +215,6 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bringUpTargetSlot = [&](size_t _targetOffset) {
|
|
||||||
std::list<size_t> toVisit{_targetOffset};
|
|
||||||
std::set<size_t> visited;
|
|
||||||
|
|
||||||
while (!toVisit.empty())
|
|
||||||
{
|
|
||||||
auto offset = *toVisit.begin();
|
|
||||||
toVisit.erase(toVisit.begin());
|
|
||||||
visited.emplace(offset);
|
|
||||||
if (ops.targetMultiplicity(offset) > 0)
|
|
||||||
{
|
|
||||||
ops.pushOrDupTarget(offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The desired target slot must already be somewhere else on stack right now.
|
|
||||||
for (auto nextOffset: ranges::views::iota(0u, std::min(ops.sourceSize(), ops.targetSize())))
|
|
||||||
if (
|
|
||||||
!ops.isCompatible(nextOffset, nextOffset) &&
|
|
||||||
ops.isCompatible(nextOffset, offset)
|
|
||||||
)
|
|
||||||
if (!visited.count(nextOffset))
|
|
||||||
toVisit.emplace_back(nextOffset);
|
|
||||||
}
|
|
||||||
yulAssert(false, "");
|
|
||||||
};
|
|
||||||
|
|
||||||
// If a lower slot should be removed, try to bring up the slot that should end up there and bring it up.
|
// If a lower slot should be removed, try to bring up the slot that should end up there and bring it up.
|
||||||
// Note that after the cases above, there will always be a target slot to duplicate in this case.
|
// Note that after the cases above, there will always be a target slot to duplicate in this case.
|
||||||
for (size_t offset: ranges::views::iota(0u, ops.sourceSize()))
|
for (size_t offset: ranges::views::iota(0u, ops.sourceSize()))
|
||||||
@ -185,7 +225,7 @@ private:
|
|||||||
!ops.targetIsArbitrary(offset) // And that target slot is not arbitrary.
|
!ops.targetIsArbitrary(offset) // And that target slot is not arbitrary.
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
bringUpTargetSlot(offset);
|
bringUpTargetSlot(ops, offset);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +249,7 @@ private:
|
|||||||
// If we still need more slots, produce a suitable one.
|
// If we still need more slots, produce a suitable one.
|
||||||
if (ops.sourceSize() < ops.targetSize())
|
if (ops.sourceSize() < ops.targetSize())
|
||||||
{
|
{
|
||||||
bringUpTargetSlot(ops.sourceSize());
|
bringUpTargetSlot(ops, ops.sourceSize());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,12 +352,6 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
|
|||||||
|
|
||||||
Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop);
|
Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop);
|
||||||
|
|
||||||
while (_currentStack.size() < _targetStack.size())
|
|
||||||
{
|
|
||||||
_pushOrDup(_targetStack.at(_currentStack.size()));
|
|
||||||
_currentStack.push_back(_targetStack.at(_currentStack.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
||||||
if (std::holds_alternative<JunkSlot>(target))
|
if (std::holds_alternative<JunkSlot>(target))
|
||||||
|
@ -549,8 +549,16 @@ Stack StackLayoutGenerator::compressStack(Stack _stack)
|
|||||||
firstDupOffset.reset();
|
firstDupOffset.reset();
|
||||||
}
|
}
|
||||||
for (auto&& [offset, slot]: _stack | ranges::views::enumerate)
|
for (auto&& [offset, slot]: _stack | ranges::views::enumerate)
|
||||||
if (canBeFreelyGenerated(slot) || util::findOffset(_stack | ranges::views::take(offset), slot))
|
if (canBeFreelyGenerated(slot))
|
||||||
|
{
|
||||||
firstDupOffset = offset;
|
firstDupOffset = offset;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (auto dupOffset = util::findOffset(_stack | ranges::views::take(offset), slot))
|
||||||
|
{
|
||||||
|
if (_stack.size() - *dupOffset <= 16)
|
||||||
|
firstDupOffset = offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (firstDupOffset);
|
while (firstDupOffset);
|
||||||
return _stack;
|
return _stack;
|
||||||
|
Loading…
Reference in New Issue
Block a user