Further customize max swap/dup.

This commit is contained in:
Daniel Kirchner 2022-12-05 14:24:32 +01:00
parent a467bdcb3c
commit 3ff457c312
17 changed files with 549 additions and 68 deletions

View File

@ -433,7 +433,7 @@ map<u256, u256> const& Assembly::optimiseInternal(
while (iter != m_items.end())
{
KnownState emptyState;
CommonSubexpressionEliminator eliminator{emptyState};
CommonSubexpressionEliminator eliminator{emptyState, maxSwap(), maxDup()};
auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end(), usesMSize);
bool shouldReplace = false;

View File

@ -62,7 +62,7 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
for (int height = minHeight; height <= m_state.stackHeight(); ++height)
targetStackContents[height] = m_state.stackElement(height, SourceLocation());
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode(
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations, m_maxSwap, m_maxDup).generateCode(
m_initialState.sequenceNumber(),
m_initialState.stackHeight(),
initialStackContents,
@ -125,9 +125,11 @@ void CommonSubexpressionEliminator::optimizeBreakingItem()
CSECodeGenerator::CSECodeGenerator(
ExpressionClasses& _expressionClasses,
vector<CSECodeGenerator::StoreOperation> const& _storeOperations
vector<CSECodeGenerator::StoreOperation> const& _storeOperations,
unsigned _maxSwap,
unsigned _maxDup
):
m_expressionClasses(_expressionClasses)
m_expressionClasses(_expressionClasses), m_maxSwap(_maxSwap), m_maxDup(_maxDup)
{
for (auto const& store: _storeOperations)
m_storeOperations[make_pair(store.target, store.slot)].push_back(store);
@ -472,7 +474,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, SourceLocation const& _locat
{
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, "");
int instructionNum = 1 + m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString);
assertThrow(instructionNum <= static_cast<int>(m_maxDup), StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(AssemblyItemType::Dup, instructionNum, _location));
m_stack[m_stackHeight] = m_stack[_fromPosition];
@ -485,7 +487,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons
if (_fromPosition == m_stackHeight)
return;
int instructionNum = m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, util::stackTooDeepString);
assertThrow(instructionNum <= static_cast<int>(m_maxSwap), StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(AssemblyItemType::Swap, instructionNum, _location));

View File

@ -66,7 +66,7 @@ public:
using Id = ExpressionClasses::Id;
using StoreOperation = KnownState::StoreOperation;
explicit CommonSubexpressionEliminator(KnownState const& _state): m_initialState(_state), m_state(_state) {}
explicit CommonSubexpressionEliminator(KnownState const& _state, unsigned _maxSwap, unsigned _maxDup): m_initialState(_state), m_state(_state), m_maxSwap(_maxSwap), m_maxDup(_maxDup) {}
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
/// item that must be fed into a new instance of the eliminator.
@ -93,6 +93,8 @@ private:
/// The item that breaks the basic block, can be nullptr.
/// It is usually appended to the block but can be optimized in some cases.
AssemblyItem const* m_breakingItem = nullptr;
unsigned m_maxSwap = 16;
unsigned m_maxDup = 16;
};
/**
@ -108,7 +110,7 @@ public:
/// Initializes the code generator with the given classes and store operations.
/// The store operations have to be sorted by sequence number in ascending order.
CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations);
CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations, unsigned _maxSwap, unsigned _maxDup);
/// @returns the assembly items generated from the given requirements
/// @param _initialSequenceNumber starting sequence number, do not generate sequenced operations
@ -169,6 +171,8 @@ private:
/// The set of equivalence classes that should be present on the stack at the end.
std::set<Id> m_finalClasses;
std::map<int, Id> m_targetStack;
unsigned m_maxSwap = 16;
unsigned m_maxDup = 16;
};
template <class AssemblyItemIterator>

View File

@ -475,7 +475,7 @@ void CompilerUtils::encodeToMemory(
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++;
assertThrow(
(argSize + dynPointers) < 16,
(argSize + dynPointers) < m_context.assembly().maxSwap(),
StackTooDeepError,
util::stackTooDeepString
);
@ -536,7 +536,7 @@ void CompilerUtils::encodeToMemory(
{
// copy tail pointer (=mem_end - mem_start) to memory
assertThrow(
(2 + dynPointers) <= 16,
(2 + dynPointers) <= m_context.assembly().maxDup(),
StackTooDeepError,
util::stackTooDeepString
);
@ -1415,7 +1415,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
unsigned const size = _variable.annotation().type->sizeOnStack();
solAssert(stackPosition >= size, "Variable size and position mismatch.");
// move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16)
if (stackPosition - size + 1 > m_context.assembly().maxSwap())
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
errinfo_sourceLocation(_variable.location()) <<
@ -1428,7 +1428,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{
assertThrow(
_stackDepth <= 16,
_stackDepth <= m_context.assembly().maxDup(),
StackTooDeepError,
util::stackTooDeepString
);
@ -1454,7 +1454,7 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
void CompilerUtils::rotateStackUp(unsigned _items)
{
assertThrow(
_items - 1 <= 16,
_items - 1 <= m_context.assembly().maxSwap(),
StackTooDeepError,
util::stackTooDeepString
);
@ -1465,7 +1465,7 @@ void CompilerUtils::rotateStackUp(unsigned _items)
void CompilerUtils::rotateStackDown(unsigned _items)
{
assertThrow(
_items - 1 <= 16,
_items - 1 <= m_context.assembly().maxSwap(),
StackTooDeepError,
util::stackTooDeepString
);

View File

@ -48,7 +48,7 @@ StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclarat
void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
{
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
if (stackPos + 1 > m_context.assembly().maxDup()) //@todo correct this by fetching earlier or moving to memory
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
@ -62,15 +62,17 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const
{
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
if (stackDiff > 16)
if (stackDiff > 0)
{
if (stackDiff > m_context.assembly().maxSwap())
BOOST_THROW_EXCEPTION(
StackTooDeepError() <<
errinfo_sourceLocation(_location) <<
util::errinfo_comment(util::stackTooDeepString)
);
else if (stackDiff > 0)
for (unsigned i = 0; i < m_size; ++i)
m_context << AssemblyItem(AssemblyItemType::Swap, stackDiff) << Instruction::POP;
}
if (!_move)
retrieveValue(_location);
}

View File

@ -180,7 +180,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
bool foundUnusedSlot = false;
for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it)
{
if (m_assembly.stackHeight() - *it > 17)
if (m_assembly.stackHeight() - *it > static_cast<int>(m_assembly.maxSwap() + 1))
continue;
foundUnusedSlot = true;
auto slot = static_cast<size_t>(*it);
@ -454,16 +454,16 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
&std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name))
)) = static_cast<int>(n);
if (stackLayout.size() > 17)
if (stackLayout.size() > m_assembly.maxSwap() + 1)
{
StackTooDeepError error(
_function.name,
YulString{},
static_cast<int>(stackLayout.size()) - 17,
static_cast<int>(stackLayout.size()) - static_cast<int>(m_assembly.maxSwap() + 1),
"The function " +
_function.name.str() +
" has " +
to_string(stackLayout.size() - 17) +
to_string(stackLayout.size() - (m_assembly.maxSwap() + 1)) +
" parameters or return variables too many to fit the stack size."
);
stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size()));
@ -777,7 +777,7 @@ size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString
yulAssert(m_context->variableStackHeights.count(&_var), "");
size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var];
yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
size_t limit = _forSwap ? 17 : 16;
size_t limit = _forSwap ? m_assembly.maxSwap() + 1 : m_assembly.maxDup();
if (heightDiff > limit)
{
m_stackErrors.emplace_back(

View File

@ -359,7 +359,9 @@ void OptimizedEVMCodeTransform::createStackLayout(std::shared_ptr<DebugData cons
[&]()
{
m_assembly.appendInstruction(evmasm::Instruction::POP);
}
},
m_assembly.maxSwap(),
m_assembly.maxDup()
);
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
}

View File

@ -127,10 +127,10 @@ private:
static bool dupDeepSlotIfRequired(ShuffleOperations& _ops)
{
// Check if the stack is large enough for anything to potentially become unreachable.
if (_ops.sourceSize() < 15)
if (_ops.sourceSize() < (_ops.maxDup() - 1))
return false;
// Check whether any deep slot might still be needed later (i.e. we still need to reach it with a DUP or SWAP).
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - 15))
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - (_ops.maxDup() - 1)))
{
// This slot needs to be moved.
if (!_ops.isCompatible(sourceOffset, sourceOffset))
@ -255,10 +255,10 @@ private:
)
{
// We cannot swap that deep.
if (ops.sourceSize() - offset - 1 > 16)
if (ops.sourceSize() - offset - 1 > ops.maxSwap())
{
// If there is a reachable slot to be removed, park the current top there.
for (size_t swapDepth: ranges::views::iota(1u, 17u) | ranges::views::reverse)
for (size_t swapDepth: ranges::views::iota(1u, ops.maxSwap() + 1) | ranges::views::reverse)
if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0)
{
ops.swap(swapDepth);
@ -326,7 +326,7 @@ private:
yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), "");
yulAssert(ops.isCompatible(sourceTop, sourceTop), "");
auto swappableOffsets = ranges::views::iota(size > 17 ? size - 17 : 0u, size);
auto swappableOffsets = ranges::views::iota(size > (ops.maxSwap() + 1) ? size - (ops.maxSwap() + 1) : 0u, size);
// If we find a lower slot that is out of position, but also compatible with the top, swap that up.
for (size_t offset: swappableOffsets)
@ -386,7 +386,7 @@ private:
/// its argument to the stack top.
/// @a _pop is a function with signature void() that is called when the top most slot is popped.
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, size_t _maxSwap, size_t _maxDup)
{
struct ShuffleOperations
{
@ -396,18 +396,24 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
PushOrDup pushOrDupCallback;
Pop popCallback;
std::map<StackSlot, int> multiplicity;
size_t const m_maxSwap = 16;
size_t const m_maxDup = 16;
ShuffleOperations(
Stack& _currentStack,
Stack const& _targetStack,
Swap _swap,
PushOrDup _pushOrDup,
Pop _pop
Pop _pop,
size_t _maxSwap,
size_t _maxDup
):
currentStack(_currentStack),
targetStack(_targetStack),
swapCallback(_swap),
pushOrDupCallback(_pushOrDup),
popCallback(_pop)
popCallback(_pop),
m_maxSwap(_maxSwap),
m_maxDup(_maxDup)
{
for (auto const& slot: currentStack)
--multiplicity[slot];
@ -417,6 +423,8 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
else
++multiplicity[slot];
}
size_t maxSwap() const { return m_maxSwap; }
size_t maxDup() const { return m_maxDup; }
bool isCompatible(size_t _source, size_t _target)
{
return
@ -454,7 +462,7 @@ 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, _maxSwap, _maxDup);
yulAssert(_currentStack.size() == _targetStack.size(), "");
for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack))

View File

@ -96,7 +96,7 @@ StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout): m_layout(_layo
namespace
{
/// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target.
vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source, Stack const& _target)
vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source, Stack const& _target, size_t _maxSwap, size_t _maxDup)
{
Stack currentStack = _source;
vector<StackLayoutGenerator::StackTooDeep> stackTooDeepErrors;
@ -113,9 +113,9 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
_target,
[&](unsigned _i)
{
if (_i > 16)
if (_i > _maxSwap)
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
_i - 16,
_i - _maxSwap,
getVariableChoices(currentStack | ranges::views::take_last(_i + 1))
});
},
@ -125,14 +125,16 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
return;
if (
auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot);
depth && *depth >= 16
depth && *depth >= _maxDup
)
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
*depth - 15,
*depth - (_maxDup - 1),
getVariableChoices(currentStack | ranges::views::take_last(*depth + 1))
});
},
[&]() {}
[&]() {},
_maxSwap,
_maxDup
);
return stackTooDeepErrors;
}
@ -142,7 +144,7 @@ vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(Stack const& _source
/// If @a _generateSlotOnTheFly returns true for a slot, this slot should not occur in the ideal stack, but
/// rather be generated on the fly during shuffling.
template<typename Callable>
Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly)
Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly, size_t _maxSwap, size_t _maxDup)
{
struct PreviousSlot { size_t slot; };
@ -174,11 +176,15 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
std::set<StackSlot> outputs;
std::map<StackSlot, int> multiplicity;
Callable generateSlotOnTheFly;
size_t m_maxSwap = 16;
size_t m_maxDup = 16;
ShuffleOperations(
vector<variant<PreviousSlot, StackSlot>>& _layout,
Stack const& _post,
Callable _generateSlotOnTheFly
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly)
Callable _generateSlotOnTheFly,
size_t _maxSwap,
size_t _maxDup
): layout(_layout), post(_post), generateSlotOnTheFly(_generateSlotOnTheFly), m_maxSwap(_maxSwap), m_maxDup(_maxDup)
{
for (auto const& layoutSlot: layout)
if (StackSlot const* slot = get_if<StackSlot>(&layoutSlot))
@ -191,6 +197,8 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
if (outputs.count(slot) || generateSlotOnTheFly(slot))
++multiplicity[slot];
}
size_t maxSwap() const { return m_maxSwap; }
size_t maxDup() const { return m_maxDup; }
bool isCompatible(size_t _source, size_t _target)
{
return
@ -241,7 +249,7 @@ 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, _post, _generateSlotOnTheFly, _maxSwap, _maxDup);
// 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
@ -280,7 +288,7 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG
// Determine the ideal permutation of the slots in _exitLayout that are not operation outputs (and not to be
// generated on the fly), s.t. shuffling the `stack + _operation.output` to _exitLayout is cheap.
Stack stack = createIdealLayout(_operation.output, _exitStack, generateSlotOnTheFly);
Stack stack = createIdealLayout(_operation.output, _exitStack, generateSlotOnTheFly, maxSwap(), maxDup());
// Make sure the resulting previous slots do not overlap with any assignmed variables.
if (auto const* assignment = get_if<CFG::Assignment>(&_operation.operation))
@ -304,7 +312,7 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG
stack.pop_back();
else if (auto offset = util::findOffset(stack | ranges::views::reverse | ranges::views::drop(1), stack.back()))
{
if (*offset + 2 < 16)
if (*offset + 2 < maxDup())
stack.pop_back();
else
break;
@ -322,7 +330,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba
for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse)
{
Stack newStack = propagateStackThroughOperation(stack, operation, _aggressiveStackCompression);
if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack).empty())
if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, maxSwap(), maxDup()).empty())
// If we had stack errors, run again with aggressive stack compression.
return propagateStackThroughBlock(std::move(_exitStack), _block, true);
stack = std::move(newStack);
@ -544,7 +552,7 @@ void StackLayoutGenerator::stitchConditionalJumps(CFG::BasicBlock const& _block)
});
}
Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _stack2)
Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _stack2) const
{
// TODO: it would be nicer to replace this by a constructive algorithm.
// Currently it uses a reduced version of the Heap Algorithm to partly brute-force, which seems
@ -580,18 +588,18 @@ Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _sta
auto evaluate = [&](Stack const& _candidate) -> size_t {
size_t numOps = 0;
Stack testStack = _candidate;
auto swap = [&](unsigned _swapDepth) { ++numOps; if (_swapDepth > 16) numOps += 1000; };
auto swap = [&](unsigned _swapDepth) { ++numOps; if (_swapDepth > maxSwap()) numOps += 1000; };
auto dupOrPush = [&](StackSlot const& _slot)
{
if (canBeFreelyGenerated(_slot))
return;
auto depth = util::findOffset(ranges::concat_view(commonPrefix, testStack) | ranges::views::reverse, _slot);
if (depth && *depth >= 16)
if (depth && *depth >= maxDup())
numOps += 1000;
};
createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){});
createStackLayout(testStack, stack1Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup());
testStack = _candidate;
createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){});
createStackLayout(testStack, stack2Tail, swap, dupOrPush, [&](){}, maxSwap(), maxDup());
return numOps;
};
@ -642,7 +650,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
{
Stack& operationEntry = m_layout.operationEntryLayout.at(&operation);
stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry);
stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, maxSwap(), maxDup());
currentStack = operationEntry;
for (size_t i = 0; i < operation.input.size(); i++)
currentStack.pop_back();
@ -656,7 +664,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
[&](CFG::BasicBlock::Jump const& _jump)
{
Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout;
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout);
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup());
if (!_jump.backwards)
_addChild(_jump.target);
@ -667,7 +675,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
m_layout.blockInfos.at(_conditionalJump.zero).entryLayout,
m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout
})
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout);
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, maxSwap(), maxDup());
_addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero);
@ -679,7 +687,7 @@ vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStackTooD
return stackTooDeepErrors;
}
Stack StackLayoutGenerator::compressStack(Stack _stack)
Stack StackLayoutGenerator::compressStack(Stack _stack) const
{
optional<size_t> firstDupOffset;
do
@ -697,7 +705,7 @@ Stack StackLayoutGenerator::compressStack(Stack _stack)
break;
}
else if (auto dupDepth = util::findOffset(_stack | ranges::views::reverse | ranges::views::drop(depth + 1), slot))
if (depth + *dupDepth <= 16)
if (depth + *dupDepth <= maxDup())
{
firstDupOffset = _stack.size() - depth - 1;
break;
@ -766,7 +774,7 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::Functi
}
};
auto pop = [&]() { opGas += evmasm::GasMeter::runGas(evmasm::Instruction::POP); };
createStackLayout(_source, _target, swap, dupOrPush, pop);
createStackLayout(_source, _target, swap, dupOrPush, pop, maxSwap(), maxDup());
return opGas;
};
/// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from

View File

@ -100,7 +100,7 @@ private:
/// Calculates the ideal stack layout, s.t. both @a _stack1 and @a _stack2 can be achieved with minimal
/// stack shuffling when starting from the returned layout.
static Stack combineStack(Stack const& _stack1, Stack const& _stack2);
Stack combineStack(Stack const& _stack1, Stack const& _stack2) const;
/// Walks through the CFG and reports any stack too deep errors that would occur when generating code for it
/// without countermeasures.
@ -109,7 +109,7 @@ private:
/// @returns a copy of @a _stack stripped of all duplicates and slots that can be freely generated.
/// Attempts to create a layout that requires a minimal amount of operations to reconstruct the original
/// stack @a _stack.
static Stack compressStack(Stack _stack);
Stack compressStack(Stack _stack) const;
//// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper.
void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr);

View File

@ -72,7 +72,7 @@ namespace
bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) {
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
});
evmasm::CommonSubexpressionEliminator cse(_state);
evmasm::CommonSubexpressionEliminator cse(_state, 16u, 16u);
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
AssemblyItems output = cse.getOptimizedItems();
@ -107,7 +107,7 @@ namespace
while (iter != _input.end())
{
KnownState emptyState;
CommonSubexpressionEliminator eliminator{emptyState};
CommonSubexpressionEliminator eliminator{emptyState, 16u, 16u};
auto orig = iter;
iter = eliminator.feedItems(iter, _input.end(), usesMSize);
bool shouldReplace = false;
@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
Instruction::ORIGIN
});
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()};
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState(), 16u, 16u};
// Make sure CSE breaks after AssignImmutable.
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2);
}
@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
{
evmasm::KnownState state;
evmasm::CommonSubexpressionEliminator cse(state);
evmasm::CommonSubexpressionEliminator cse(state, 16u, 16u);
AssemblyItems input{
Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1,
Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1,

View File

@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE(swap_cycle)
FunctionReturnLabelSlot{function}, JunkSlot{}, JunkSlot{}
};
// Used to hit a swapping cycle.
createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){});
createStackLayout(sourceStack, targetStack, [](auto){}, [](auto){}, [](){}, 16u, 16u);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,24 @@
{
pop(addmod(addmod(0x80000000000000000000000000000000000, 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC, 0x800000000000000000000000000000000000), 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC, 0x8000000000000000000000000000000000000))
}
// ====
// stackOptimization: true
// ----
// /* "":240:279 */
// 0x08000000000000000000000000000000000000
// /* "":172:238 */
// 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
// /* "":131:169 */
// 0x800000000000000000000000000000000000
// /* "":17:170 */
// dup2
// /* "":24:61 */
// 0x080000000000000000000000000000000000
// /* "":17:170 */
// addmod
// /* "":10:280 */
// addmod
// /* "":6:281 */
// pop
// /* "":0:283 */
// stop

View File

@ -0,0 +1,105 @@
{
for { let i_0 := 0 } lt(i_0, 0x60) { i_0 := add(i_0, 0x20) }
{
switch mload(0x1fffffffffffffffffffffffff)
case 0x1ffffffffffffffffffffffffff { }
default {
for { let i_1 := 0 } lt(i_1, 0x60) { i_1 := add(i_1, 0x20) }
{
switch 0x1fffffffffffffffffffffffffff
case 0x1ffffffffffffffffffffffffffff { }
default { break }
continue
let x_4, x_5
}
}
let x_6, x_7
}
}
// ====
// stackOptimization: true
// ----
// /* "":23:24 */
// 0x00
// /* "":27:40 */
// tag_1:
// /* "":35:39 */
// 0x60
// /* "":27:40 */
// dup2
// lt
// tag_2
// jumpi
// /* "":6:527 */
// tag_3:
// stop
// /* "":71:527 */
// tag_2:
// /* "":59:63 */
// 0x20
// /* "":88:123 */
// swap1
// /* "":94:122 */
// 0x1fffffffffffffffffffffffff
// /* "":88:123 */
// mload
// /* "":137:166 */
// 0x01ffffffffffffffffffffffffff
// /* "":132:170 */
// eq
// tag_4
// jumpi
// /* "":81:500 */
// tag_5:
// /* "":218:219 */
// 0x00
// /* "":222:235 */
// tag_6:
// /* "":230:234 */
// 0x60
// /* "":222:235 */
// dup2
// lt
// tag_7
// jumpi
// /* "":201:490 */
// tag_8:
// /* "":187:500 */
// pop
// /* "":81:500 */
// tag_9:
// /* "":509:521 */
// 0x00
// dup1
// /* "":71:527 */
// pop
// pop
// /* "":50:64 */
// add
// /* "":41:66 */
// jump(tag_1)
// /* "":274:490 */
// tag_7:
// /* "":299:329 */
// 0x1fffffffffffffffffffffffffff
// /* "":353:384 */
// 0x01ffffffffffffffffffffffffffff
// /* "":346:388 */
// eq
// tag_10
// jumpi
// /* "":292:422 */
// tag_11:
// /* "":415:420 */
// jump(tag_8)
// /* "":385:388 */
// tag_10:
// dup3
// swap1
// /* "":245:259 */
// add
// /* "":236:261 */
// jump(tag_6)
// /* "":167:170 */
// tag_4:
// jump(tag_9)

View File

@ -0,0 +1,132 @@
{
for { let i_0 := 0 } lt(i_0, 0x60) { i_0 := add(i_0, 0x20) }
{
switch mload(0x1fffffffffffffffffffffffff)
case 0x1ffffffffffffffffffffffffff { }
default {
for { let i_1 := 0 } lt(i_1, 0x60) { i_1 := add(i_1, 0x20) }
{
switch 0x1fffffffffffffffffffffffffff
case 0x1ffffffffffffffffffffffffffff { }
default { break }
continue
let x_4, x_5
}
}
let x_6, x_7
}
}
// ====
// stackOptimization: false
// ----
// /* "":23:24 */
// 0x00
// /* "":6:527 */
// tag_1:
// /* "":35:39 */
// 0x60
// /* "":30:33 */
// dup2
// /* "":27:40 */
// lt
// /* "":6:527 */
// iszero
// tag_3
// jumpi
// /* "":94:122 */
// 0x1fffffffffffffffffffffffff
// /* "":88:123 */
// mload
// /* "":137:166 */
// 0x01ffffffffffffffffffffffffff
// /* "":132:170 */
// dup2
// eq
// tag_5
// jumpi
// /* "":218:219 */
// 0x00
// /* "":201:490 */
// tag_6:
// /* "":230:234 */
// 0x60
// /* "":225:228 */
// dup2
// /* "":222:235 */
// lt
// /* "":201:490 */
// iszero
// tag_8
// jumpi
// /* "":299:329 */
// 0x1fffffffffffffffffffffffffff
// /* "":353:384 */
// 0x01ffffffffffffffffffffffffffff
// /* "":346:388 */
// dup2
// eq
// tag_10
// jumpi
// /* "":415:420 */
// pop
// jump(tag_8)
// /* "":292:422 */
// jump(tag_9)
// /* "":346:388 */
// tag_10:
// /* "":292:422 */
// tag_9:
// pop
// /* "":439:447 */
// jump(tag_7)
// /* "":464:476 */
// 0x00
// 0x00
// /* "":274:490 */
// pop
// pop
// /* "":201:490 */
// tag_7:
// /* "":254:258 */
// 0x20
// /* "":249:252 */
// dup2
// /* "":245:259 */
// add
// /* "":238:259 */
// swap1
// pop
// /* "":201:490 */
// jump(tag_6)
// tag_8:
// /* "":205:221 */
// pop
// /* "":81:500 */
// jump(tag_4)
// /* "":132:170 */
// tag_5:
// /* "":81:500 */
// tag_4:
// pop
// /* "":509:521 */
// 0x00
// 0x00
// /* "":71:527 */
// pop
// pop
// /* "":6:527 */
// tag_2:
// /* "":59:63 */
// 0x20
// /* "":54:57 */
// dup2
// /* "":50:64 */
// add
// /* "":43:64 */
// swap1
// pop
// /* "":6:527 */
// jump(tag_1)
// tag_3:
// /* "":10:26 */
// pop

View File

@ -0,0 +1,105 @@
{
{
let i := 0
let i_1 := i
let _1 := mload(0x1fffffffffffffffffffffffff)
for { } true { i_1 := add(i_1, 0x20) }
{
let _2 := 0x60
if iszero(lt(i_1, _2)) { break }
switch _1
case 0x1ffffffffffffffffffffffffff { }
default {
let i_2 := i
if lt(i, _2) { }
}
}
}
}
// ----
// /* "":25:26 */
// 0x00
// /* "":46:47 */
// dup1
// /* "":72:100 */
// 0x1fffffffffffffffffffffffff
// /* "":66:101 */
// mload
// /* "":110:411 */
// tag_1:
// /* "":118:122 */
// 0x01
// /* "":110:411 */
// iszero
// tag_3
// jumpi
// /* "":181:185 */
// 0x60
// /* "":216:218 */
// dup1
// /* "":211:214 */
// dup4
// /* "":208:219 */
// lt
// /* "":201:220 */
// iszero
// /* "":198:230 */
// iszero
// tag_4
// jumpi
// /* "":223:228 */
// pop
// jump(tag_3)
// /* "":198:230 */
// tag_4:
// /* "":250:252 */
// dup2
// /* "":270:299 */
// 0x01ffffffffffffffffffffffffff
// /* "":265:303 */
// dup2
// eq
// tag_6
// jumpi
// /* "":353:354 */
// dup5
// /* "":380:382 */
// dup3
// /* "":377:378 */
// dup7
// /* "":374:383 */
// lt
// /* "":371:387 */
// iszero
// tag_7
// jumpi
// tag_7:
// /* "":324:401 */
// pop
// /* "":243:401 */
// jump(tag_5)
// /* "":265:303 */
// tag_6:
// /* "":243:401 */
// tag_5:
// pop
// /* "":157:411 */
// pop
// /* "":110:411 */
// tag_2:
// /* "":141:145 */
// 0x20
// /* "":136:139 */
// dup3
// /* "":132:146 */
// add
// /* "":125:146 */
// swap2
// pop
// /* "":110:411 */
// jump(tag_1)
// tag_3:
// /* "":6:417 */
// pop
// pop
// pop

View File

@ -0,0 +1,89 @@
{
{
let i := 0
let i_1 := i
let _1 := mload(0x1fffffffffffffffffffffffff)
for { } true { i_1 := add(i_1, 0x20) }
{
let _2 := 0x60
if iszero(lt(i_1, _2)) { break }
switch _1
case 0x1ffffffffffffffffffffffffff { }
default {
let i_2 := i
if lt(i, _2) { }
}
}
}
}
// ====
// stackOptimization: true
// ----
// /* "":25:26 */
// 0x00
// /* "":35:47 */
// dup1
// /* "":66:101 */
// swap1
// /* "":72:100 */
// 0x1fffffffffffffffffffffffff
// /* "":66:101 */
// mload
// /* "":114:117 */
// swap2
// /* "":157:411 */
// tag_1:
// /* "":181:185 */
// 0x60
// /* "":208:219 */
// swap1
// dup2
// dup2
// lt
// /* "":201:220 */
// iszero
// /* "":198:230 */
// tag_2
// jumpi
// /* "":157:411 */
// tag_3:
// /* "":141:145 */
// 0x20
// /* "":243:401 */
// swap2
// dup5
// /* "":270:299 */
// 0x01ffffffffffffffffffffffffff
// /* "":265:303 */
// eq
// tag_4
// jumpi
// /* "":243:401 */
// tag_5:
// /* "":342:354 */
// dup4
// /* "":374:383 */
// pop
// dup4
// lt
// /* "":371:387 */
// tag_6
// jumpi
// /* "":243:401 */
// tag_7:
// tag_8:
// /* "":132:146 */
// add
// /* "":123:148 */
// jump(tag_1)
// /* "":384:387 */
// tag_6:
// jump(tag_7)
// /* "":300:303 */
// tag_4:
// pop
// jump(tag_8)
// /* "":221:230 */
// tag_2:
// /* "":110:411 */
// stop