Skip to content

Commit 6e06e46

Browse files
committed
Support for SWAPN/DUPN in EVM Code Transform.
1 parent 98b1df3 commit 6e06e46

File tree

12 files changed

+183
-72
lines changed

12 files changed

+183
-72
lines changed

libevmasm/GasMeter.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ unsigned GasMeter::pushGas(u256 _value, langutil::EVMVersion _evmVersion)
299299
);
300300
}
301301

302+
unsigned GasMeter::swapGas(size_t)
303+
{
304+
return 3;
305+
}
306+
307+
unsigned GasMeter::dupGas(size_t)
308+
{
309+
return 3;
310+
}
311+
302312
u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion)
303313
{
304314
bigint gas = 0;

libevmasm/GasMeter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ class GasMeter
229229
/// @returns gas costs for push instructions (may change depending on EVM version)
230230
static unsigned pushGas(u256 _value, langutil::EVMVersion _evmVersion);
231231

232+
/// @returns gas costs for swap instructions (may change depending on EVM version)
233+
static unsigned swapGas(size_t _depth);
234+
235+
/// @returns gas costs for dup instructions (may change depending on EVM version)
236+
static unsigned dupGas(size_t _depth);
237+
232238
/// @returns the gas cost of the supplied data, depending whether it is in creation code, or not.
233239
/// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas
234240
/// otherwise code will be stored and have to pay "createDataGas" cost.

libyul/backends/evm/EVMDialect.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class EVMDialect: public Dialect
104104

105105
langutil::EVMVersion evmVersion() const { return m_evmVersion; }
106106
std::optional<uint8_t> eofVersion() const { return m_eofVersion; }
107+
size_t reachableStackDepth() const { return m_eofVersion.has_value() ? 256 : 16; }
107108

108109
bool providesObjectAccess() const { return m_objectAccess; }
109110

libyul/backends/evm/OptimizedEVMCodeTransform.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include <libyul/Utilities.h>
2525

26+
#include <libevmasm/Assembly.h>
2627
#include <libevmasm/Instruction.h>
2728

2829
#include <libsolutil/Visitor.h>
@@ -48,7 +49,7 @@ std::vector<StackTooDeepError> OptimizedEVMCodeTransform::run(
4849
)
4950
{
5051
std::unique_ptr<CFG> dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _block);
51-
StackLayout stackLayout = StackLayoutGenerator::run(*dfg, !_dialect.eofVersion().has_value());
52+
StackLayout stackLayout = StackLayoutGenerator::run(*dfg, !_dialect.eofVersion().has_value(), _dialect.reachableStackDepth());
5253

5354
if (_dialect.eofVersion().has_value())
5455
{
@@ -229,7 +230,8 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform(
229230
}
230231
return functionLabels;
231232
}()),
232-
m_simulateFunctionsWithJumps(_simulateFunctionsWithJumps)
233+
m_simulateFunctionsWithJumps(_simulateFunctionsWithJumps),
234+
m_reachableStackDepth(_dialect.reachableStackDepth())
233235
{
234236
}
235237

@@ -285,11 +287,11 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
285287
{
286288
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight(), "");
287289
yulAssert(_i > 0 && _i < m_stack.size(), "");
288-
if (_i <= 16)
289-
m_assembly.appendInstruction(evmasm::swapInstruction(_i));
290+
if (_i <= m_reachableStackDepth)
291+
appendSwap(_i);
290292
else
291293
{
292-
int deficit = static_cast<int>(_i) - 16;
294+
int deficit = static_cast<int>(_i) - static_cast<int>(m_reachableStackDepth);
293295
StackSlot const& deepSlot = m_stack.at(m_stack.size() - _i - 1);
294296
YulName varNameDeep = slotVariableName(deepSlot);
295297
YulName varNameTop = slotVariableName(m_stack.back());
@@ -310,22 +312,21 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
310312
[&](StackSlot const& _slot)
311313
{
312314
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight(), "");
313-
314315
// Dup the slot, if already on stack and reachable.
315316
if (auto depth = util::findOffset(m_stack | ranges::views::reverse, _slot))
316317
{
317-
if (*depth < 16)
318+
if (*depth < m_reachableStackDepth)
318319
{
319-
m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(*depth + 1)));
320+
appendDup(*depth + 1);
320321
return;
321322
}
322323
else if (!canBeFreelyGenerated(_slot))
323324
{
324-
int deficit = static_cast<int>(*depth - 15);
325+
int deficit = static_cast<int>(*depth - (m_reachableStackDepth - 1));
325326
YulName varName = slotVariableName(_slot);
326327
std::string msg =
327328
(varName.empty() ? "Slot " + stackSlotToString(_slot, m_dialect) : "Variable " + varName.str())
328-
+ " is " + std::to_string(*depth - 15) + " too deep in the stack " + stackToString(m_stack, m_dialect);
329+
+ " is " + std::to_string(*depth - (m_reachableStackDepth - 1)) + " too deep in the stack " + stackToString(m_stack, m_dialect);
329330
m_stackErrors.emplace_back(StackTooDeepError(
330331
m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulName{},
331332
varName,
@@ -388,11 +389,34 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
388389
[&]()
389390
{
390391
m_assembly.appendInstruction(evmasm::Instruction::POP);
391-
}
392+
},
393+
m_reachableStackDepth
392394
);
393395
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
394396
}
395397

398+
void OptimizedEVMCodeTransform::appendSwap(size_t _depth)
399+
{
400+
if (_depth <= 16)
401+
m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(_depth)));
402+
else
403+
{
404+
yulAssert(_depth >= 1 && _depth <= 256);
405+
m_assembly.appendSwapN(static_cast<uint8_t>(_depth - 1));
406+
}
407+
}
408+
409+
void OptimizedEVMCodeTransform::appendDup(size_t _depth)
410+
{
411+
if (_depth <= 16)
412+
m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(_depth)));
413+
else
414+
{
415+
yulAssert(_depth >= 1 && _depth <= 256);
416+
m_assembly.appendDupN(static_cast<uint8_t>(_depth - 1));
417+
}
418+
}
419+
396420
void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
397421
{
398422
// Assert that this is the first visit of the block and mark as generated.

libyul/backends/evm/OptimizedEVMCodeTransform.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class OptimizedEVMCodeTransform
8787
/// Sets the source locations to the one in @a _debugData.
8888
void createStackLayout(langutil::DebugData::ConstPtr _debugData, Stack _targetStack);
8989

90+
void appendSwap(size_t _depth);
91+
void appendDup(size_t _depth);
92+
9093
/// Generate code for the given block @a _block.
9194
/// Expects the current stack layout m_stack to be a stack layout that is compatible with the
9295
/// entry layout expected by the block.
@@ -116,6 +119,7 @@ class OptimizedEVMCodeTransform
116119
std::vector<StackTooDeepError> m_stackErrors;
117120
/// True if it simulates functions with jumps. False otherwise. True for legacy bytecode
118121
bool m_simulateFunctionsWithJumps = true;
122+
size_t const m_reachableStackDepth = 16;
119123
};
120124

121125
}

libyul/backends/evm/StackHelpers.h

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ inline std::string stackToString(Stack const& _stack, Dialect const& _dialect)
6565
// in the interface below.
6666
// Based on that information the shuffler decides which is the next optimal operation to perform on the stack
6767
// and calls the corresponding entry point in the shuffling operations (swap, pushOrDupTarget or pop).
68-
/*
6968
template<typename ShuffleOperations>
7069
concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceOffset, size_t targetOffset, size_t depth) {
7170
// Returns true, iff the current slot at sourceOffset in source layout is a suitable slot at targetOffset.
@@ -98,11 +97,13 @@ concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceO
9897
{ ops.pop() };
9998
// Dups or pushes the slot that is supposed to end up at the given target offset.
10099
{ ops.pushOrDupTarget(targetOffset) };
100+
// Maximum reachable depth with swaps and dups.
101+
{ ops.reachableStackDepth } -> std::convertible_to<size_t>;
101102
};
102-
*/
103+
103104
/// Helper class that can perform shuffling of a source stack layout to a target stack layout via
104105
/// abstracted shuffle operations.
105-
template</*ShuffleOperationConcept*/ typename ShuffleOperations>
106+
template<ShuffleOperationConcept ShuffleOperations>
106107
class Shuffler
107108
{
108109
public:
@@ -128,10 +129,10 @@ class Shuffler
128129
static bool dupDeepSlotIfRequired(ShuffleOperations& _ops)
129130
{
130131
// Check if the stack is large enough for anything to potentially become unreachable.
131-
if (_ops.sourceSize() < 15)
132+
if (_ops.sourceSize() < (_ops.reachableStackDepth - 1))
132133
return false;
133134
// Check whether any deep slot might still be needed later (i.e. we still need to reach it with a DUP or SWAP).
134-
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - 15))
135+
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - (_ops.reachableStackDepth - 1)))
135136
{
136137
// This slot needs to be moved.
137138
if (!_ops.isCompatible(sourceOffset, sourceOffset))
@@ -256,10 +257,10 @@ class Shuffler
256257
)
257258
{
258259
// We cannot swap that deep.
259-
if (ops.sourceSize() - offset - 1 > 16)
260+
if (ops.sourceSize() - offset - 1 > ops.reachableStackDepth)
260261
{
261262
// If there is a reachable slot to be removed, park the current top there.
262-
for (size_t swapDepth: ranges::views::iota(1u, 17u) | ranges::views::reverse)
263+
for (size_t swapDepth: ranges::views::iota(1u, ops.reachableStackDepth + 1u) | ranges::views::reverse)
263264
if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0)
264265
{
265266
ops.swap(swapDepth);
@@ -431,7 +432,14 @@ class Multiplicity
431432
/// its argument to the stack top.
432433
/// @a _pop is a function with signature void() that is called when the top most slot is popped.
433434
template<typename Swap, typename PushOrDup, typename Pop>
434-
void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop)
435+
void createStackLayout(
436+
Stack& _currentStack,
437+
Stack const& _targetStack,
438+
Swap _swap,
439+
PushOrDup _pushOrDup,
440+
Pop _pop,
441+
size_t _reachableStackDepth
442+
)
435443
{
436444
struct ShuffleOperations
437445
{
@@ -441,18 +449,21 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
441449
PushOrDup pushOrDupCallback;
442450
Pop popCallback;
443451
Multiplicity multiplicity;
452+
size_t reachableStackDepth;
444453
ShuffleOperations(
445454
Stack& _currentStack,
446455
Stack const& _targetStack,
447456
Swap _swap,
448457
PushOrDup _pushOrDup,
449-
Pop _pop
458+
Pop _pop,
459+
size_t _reachableStackDepth
450460
):
451461
currentStack(_currentStack),
452462
targetStack(_targetStack),
453463
swapCallback(_swap),
454464
pushOrDupCallback(_pushOrDup),
455-
popCallback(_pop)
465+
popCallback(_pop),
466+
reachableStackDepth(_reachableStackDepth)
456467
{
457468
for (auto const& slot: currentStack)
458469
--multiplicity[slot];
@@ -499,7 +510,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
499510
}
500511
};
501512

502-
Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop);
513+
Shuffler<ShuffleOperations>::shuffle(_currentStack, _targetStack, _swap, _pushOrDup, _pop, _reachableStackDepth);
503514

504515
yulAssert(_currentStack.size() == _targetStack.size(), "");
505516
for (auto&& [current, target]: ranges::zip_view(_currentStack, _targetStack))

0 commit comments

Comments
 (0)