Skip to content

Commit 5879562

Browse files
authored
Merge pull request #15844 from ethereum/eofSwapnDupn
eof: Make use of EOF's SWAPN/DUPN
2 parents 3cfd931 + 9fdf9b5 commit 5879562

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+6017
-110
lines changed

libevmasm/GasMeter.cpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,11 @@ GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosS
263263
}));
264264
}
265265

266-
unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVersion)
266+
namespace
267267
{
268-
if (_instruction == Instruction::JUMPDEST)
269-
return 1;
270-
271-
switch (instructionInfo(_instruction, _evmVersion).gasPriceTier)
268+
std::optional<unsigned> gasCostForTier(Tier _tier)
269+
{
270+
switch (_tier)
272271
{
273272
case Tier::Zero: return GasCosts::tier0Gas;
274273
case Tier::Base: return GasCosts::tier1Gas;
@@ -286,10 +285,21 @@ unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVer
286285

287286
case Tier::Special:
288287
case Tier::Invalid:
289-
assertThrow(false, OptimizerException, "Invalid gas tier for instruction " + instructionInfo(_instruction, _evmVersion).name);
288+
return std::nullopt;
290289
}
291290
util::unreachable();
292291
}
292+
}
293+
294+
unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVersion)
295+
{
296+
if (_instruction == Instruction::JUMPDEST)
297+
return 1;
298+
299+
if (auto gasCost = gasCostForTier(instructionInfo(_instruction, _evmVersion).gasPriceTier))
300+
return *gasCost;
301+
solAssert(false, "Invalid gas tier for instruction " + instructionInfo(_instruction, _evmVersion).name);
302+
}
293303

294304
unsigned GasMeter::pushGas(u256 _value, langutil::EVMVersion _evmVersion)
295305
{
@@ -299,6 +309,24 @@ unsigned GasMeter::pushGas(u256 _value, langutil::EVMVersion _evmVersion)
299309
);
300310
}
301311

312+
unsigned GasMeter::swapGas(size_t _depth, langutil::EVMVersion _evmVersion)
313+
{
314+
if (_depth <= 16)
315+
return runGas(evmasm::swapInstruction(static_cast<unsigned>(_depth)), _evmVersion);
316+
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::SWAPN, _evmVersion).gasPriceTier);
317+
solAssert(gasCost.has_value(), "Expected gas cost for SWAPN to be defined.");
318+
return *gasCost;
319+
}
320+
321+
unsigned GasMeter::dupGas(size_t _depth, langutil::EVMVersion _evmVersion)
322+
{
323+
if (_depth <= 16)
324+
return runGas(evmasm::swapInstruction(static_cast<unsigned>(_depth)), _evmVersion);
325+
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::DUPN, _evmVersion).gasPriceTier);
326+
solAssert(gasCost.has_value(), "Expected gas cost for DUPN to be defined.");
327+
return *gasCost;
328+
}
329+
302330
u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion)
303331
{
304332
bigint gas = 0;
@@ -309,14 +337,14 @@ u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersio
309337
}
310338
else
311339
gas = bigint(GasCosts::createDataGas) * _data.size();
312-
assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits.");
340+
solAssert(gas < bigint(u256(-1)), "Gas cost exceeds 256 bits.");
313341
return u256(gas);
314342
}
315343

316344

317345
u256 GasMeter::dataGas(uint64_t _length, bool _inCreation, langutil::EVMVersion _evmVersion)
318346
{
319347
bigint gas = bigint(_length) * (_inCreation ? GasCosts::txDataNonZeroGas(_evmVersion) : GasCosts::createDataGas);
320-
assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits.");
348+
solAssert(gas < bigint(u256(-1)), "Gas cost exceeds 256 bits.");
321349
return u256(gas);
322350
}

libevmasm/GasMeter.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,18 @@ class GasMeter
226226
/// change with EVM versions)
227227
static unsigned runGas(Instruction _instruction, langutil::EVMVersion _evmVersion);
228228

229-
/// @returns gas costs for push instructions (may change depending on EVM version)
229+
/// @returns gas costs for the cheapest push instructions for the given @a _value
230+
/// (may change depending on EVM version)
230231
static unsigned pushGas(u256 _value, langutil::EVMVersion _evmVersion);
231232

233+
/// @returns gas costs for the cheapest swap instructions for the given @a _depth
234+
/// (may change depending on EVM version)
235+
static unsigned swapGas(size_t _depth, langutil::EVMVersion _evmVersion);
236+
237+
/// @returns gas costs for the cheapest dup instructions for the given @a _depth
238+
/// (may change depending on EVM version)
239+
static unsigned dupGas(size_t _depth, langutil::EVMVersion _evmVersion);
240+
232241
/// @returns the gas cost of the supplied data, depending whether it is in creation code, or not.
233242
/// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas
234243
/// 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);
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 <= m_reachableStackDepth);
405+
m_assembly.appendSwapN(_depth);
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 <= m_reachableStackDepth);
416+
m_assembly.appendDupN(_depth);
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{};
119123
};
120124

121125
}

libyul/backends/evm/StackHelpers.h

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ inline std::string stackToString(Stack const& _stack, Dialect const& _dialect)
5656
}
5757

5858

59-
// Abstraction of stack shuffling operations. Can be defined as actual concept once we switch to C++20.
6059
// Used as an interface for the stack shuffler below.
6160
// The shuffle operation class is expected to internally keep track of a current stack layout (the "source layout")
6261
// that the shuffler is supposed to shuffle to a fixed target stack layout.
@@ -65,7 +64,6 @@ inline std::string stackToString(Stack const& _stack, Dialect const& _dialect)
6564
// in the interface below.
6665
// Based on that information the shuffler decides which is the next optimal operation to perform on the stack
6766
// and calls the corresponding entry point in the shuffling operations (swap, pushOrDupTarget or pop).
68-
/*
6967
template<typename ShuffleOperations>
7068
concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceOffset, size_t targetOffset, size_t depth) {
7169
// Returns true, iff the current slot at sourceOffset in source layout is a suitable slot at targetOffset.
@@ -98,11 +96,13 @@ concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceO
9896
{ ops.pop() };
9997
// Dups or pushes the slot that is supposed to end up at the given target offset.
10098
{ ops.pushOrDupTarget(targetOffset) };
99+
// Maximum reachable depth with swaps and dups.
100+
{ ops.reachableStackDepth } -> std::convertible_to<size_t>;
101101
};
102-
*/
102+
103103
/// Helper class that can perform shuffling of a source stack layout to a target stack layout via
104104
/// abstracted shuffle operations.
105-
template</*ShuffleOperationConcept*/ typename ShuffleOperations>
105+
template<ShuffleOperationConcept ShuffleOperations>
106106
class Shuffler
107107
{
108108
public:
@@ -128,10 +128,10 @@ class Shuffler
128128
static bool dupDeepSlotIfRequired(ShuffleOperations& _ops)
129129
{
130130
// Check if the stack is large enough for anything to potentially become unreachable.
131-
if (_ops.sourceSize() < 15)
131+
if (_ops.sourceSize() < (_ops.reachableStackDepth - 1))
132132
return false;
133133
// 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))
134+
for (size_t sourceOffset: ranges::views::iota(0u, _ops.sourceSize() - (_ops.reachableStackDepth - 1)))
135135
{
136136
// This slot needs to be moved.
137137
if (!_ops.isCompatible(sourceOffset, sourceOffset))
@@ -256,10 +256,10 @@ class Shuffler
256256
)
257257
{
258258
// We cannot swap that deep.
259-
if (ops.sourceSize() - offset - 1 > 16)
259+
if (ops.sourceSize() - offset - 1 > ops.reachableStackDepth)
260260
{
261261
// 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)
262+
for (size_t swapDepth: ranges::views::iota(1u, ops.reachableStackDepth + 1u) | ranges::views::reverse)
263263
if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0)
264264
{
265265
ops.swap(swapDepth);
@@ -327,7 +327,7 @@ class Shuffler
327327
yulAssert(ops.sourceMultiplicity(i) == 0 && (ops.targetIsArbitrary(i) || ops.targetMultiplicity(i) == 0), "");
328328
yulAssert(ops.isCompatible(sourceTop, sourceTop), "");
329329

330-
auto swappableOffsets = ranges::views::iota(size > 17 ? size - 17 : 0u, size);
330+
auto swappableOffsets = ranges::views::iota(size > ops.reachableStackDepth + 1u ? size - (ops.reachableStackDepth + 1u) : 0u, size);
331331

332332
// If we find a lower slot that is out of position, but also compatible with the top, swap that up.
333333
for (size_t offset: swappableOffsets)
@@ -431,7 +431,14 @@ class Multiplicity
431431
/// its argument to the stack top.
432432
/// @a _pop is a function with signature void() that is called when the top most slot is popped.
433433
template<typename Swap, typename PushOrDup, typename Pop>
434-
void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _swap, PushOrDup _pushOrDup, Pop _pop)
434+
void createStackLayout(
435+
Stack& _currentStack,
436+
Stack const& _targetStack,
437+
Swap _swap,
438+
PushOrDup _pushOrDup,
439+
Pop _pop,
440+
size_t _reachableStackDepth
441+
)
435442
{
436443
struct ShuffleOperations
437444
{
@@ -441,18 +448,21 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
441448
PushOrDup pushOrDupCallback;
442449
Pop popCallback;
443450
Multiplicity multiplicity;
451+
size_t reachableStackDepth;
444452
ShuffleOperations(
445453
Stack& _currentStack,
446454
Stack const& _targetStack,
447455
Swap _swap,
448456
PushOrDup _pushOrDup,
449-
Pop _pop
457+
Pop _pop,
458+
size_t _reachableStackDepth
450459
):
451460
currentStack(_currentStack),
452461
targetStack(_targetStack),
453462
swapCallback(_swap),
454463
pushOrDupCallback(_pushOrDup),
455-
popCallback(_pop)
464+
popCallback(_pop),
465+
reachableStackDepth(_reachableStackDepth)
456466
{
457467
for (auto const& slot: currentStack)
458468
--multiplicity[slot];
@@ -499,7 +509,7 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
499509
}
500510
};
501511

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

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

0 commit comments

Comments
 (0)