Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,11 @@ GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosS
}));
}

unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVersion)
namespace
{
if (_instruction == Instruction::JUMPDEST)
return 1;

switch (instructionInfo(_instruction, _evmVersion).gasPriceTier)
std::optional<unsigned> gasCostForTier(Tier _tier)
{
switch (_tier)
{
case Tier::Zero: return GasCosts::tier0Gas;
case Tier::Base: return GasCosts::tier1Gas;
Expand All @@ -286,10 +285,21 @@ unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVer

case Tier::Special:
case Tier::Invalid:
assertThrow(false, OptimizerException, "Invalid gas tier for instruction " + instructionInfo(_instruction, _evmVersion).name);
return std::nullopt;
}
util::unreachable();
}
}

unsigned GasMeter::runGas(Instruction _instruction, langutil::EVMVersion _evmVersion)
{
if (_instruction == Instruction::JUMPDEST)
return 1;

if (auto gasCost = gasCostForTier(instructionInfo(_instruction, _evmVersion).gasPriceTier))
return *gasCost;
solAssert(false, "Invalid gas tier for instruction " + instructionInfo(_instruction, _evmVersion).name);
}

unsigned GasMeter::pushGas(u256 _value, langutil::EVMVersion _evmVersion)
{
Expand All @@ -299,6 +309,24 @@ unsigned GasMeter::pushGas(u256 _value, langutil::EVMVersion _evmVersion)
);
}

unsigned GasMeter::swapGas(size_t _depth, langutil::EVMVersion _evmVersion)
{
if (_depth <= 16)
return runGas(evmasm::swapInstruction(static_cast<unsigned>(_depth)), _evmVersion);
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::SWAPN, _evmVersion).gasPriceTier);
solAssert(gasCost.has_value(), "Expected gas cost for SWAPN to be defined.");
return *gasCost;
}

unsigned GasMeter::dupGas(size_t _depth, langutil::EVMVersion _evmVersion)
{
if (_depth <= 16)
return runGas(evmasm::swapInstruction(static_cast<unsigned>(_depth)), _evmVersion);
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::DUPN, _evmVersion).gasPriceTier);
solAssert(gasCost.has_value(), "Expected gas cost for DUPN to be defined.");
return *gasCost;
}

u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion)
{
bigint gas = 0;
Expand All @@ -309,14 +337,14 @@ u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersio
}
else
gas = bigint(GasCosts::createDataGas) * _data.size();
assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits.");
solAssert(gas < bigint(u256(-1)), "Gas cost exceeds 256 bits.");
return u256(gas);
}


u256 GasMeter::dataGas(uint64_t _length, bool _inCreation, langutil::EVMVersion _evmVersion)
{
bigint gas = bigint(_length) * (_inCreation ? GasCosts::txDataNonZeroGas(_evmVersion) : GasCosts::createDataGas);
assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits.");
solAssert(gas < bigint(u256(-1)), "Gas cost exceeds 256 bits.");
return u256(gas);
}
11 changes: 10 additions & 1 deletion libevmasm/GasMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,18 @@ class GasMeter
/// change with EVM versions)
static unsigned runGas(Instruction _instruction, langutil::EVMVersion _evmVersion);

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

/// @returns gas costs for the cheapest swap instructions for the given @a _depth
/// (may change depending on EVM version)
static unsigned swapGas(size_t _depth, langutil::EVMVersion _evmVersion);

/// @returns gas costs for the cheapest dup instructions for the given @a _depth
/// (may change depending on EVM version)
static unsigned dupGas(size_t _depth, langutil::EVMVersion _evmVersion);

/// @returns the gas cost of the supplied data, depending whether it is in creation code, or not.
/// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas
/// otherwise code will be stored and have to pay "createDataGas" cost.
Expand Down
1 change: 1 addition & 0 deletions libyul/backends/evm/EVMDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class EVMDialect: public Dialect

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

bool providesObjectAccess() const { return m_objectAccess; }

Expand Down
46 changes: 35 additions & 11 deletions libyul/backends/evm/OptimizedEVMCodeTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <libyul/Utilities.h>

#include <libevmasm/Assembly.h>
#include <libevmasm/Instruction.h>

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

if (_dialect.eofVersion().has_value())
{
Expand Down Expand Up @@ -229,7 +230,8 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform(
}
return functionLabels;
}()),
m_simulateFunctionsWithJumps(_simulateFunctionsWithJumps)
m_simulateFunctionsWithJumps(_simulateFunctionsWithJumps),
m_reachableStackDepth(_dialect.reachableStackDepth())
{
}

Expand Down Expand Up @@ -285,11 +287,11 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
{
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight(), "");
yulAssert(_i > 0 && _i < m_stack.size(), "");
if (_i <= 16)
m_assembly.appendInstruction(evmasm::swapInstruction(_i));
if (_i <= m_reachableStackDepth)
appendSwap(_i);
else
{
int deficit = static_cast<int>(_i) - 16;
int deficit = static_cast<int>(_i) - static_cast<int>(m_reachableStackDepth);
StackSlot const& deepSlot = m_stack.at(m_stack.size() - _i - 1);
YulName varNameDeep = slotVariableName(deepSlot);
YulName varNameTop = slotVariableName(m_stack.back());
Expand All @@ -310,22 +312,21 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
[&](StackSlot const& _slot)
{
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight(), "");

// Dup the slot, if already on stack and reachable.
if (auto depth = util::findOffset(m_stack | ranges::views::reverse, _slot))
{
if (*depth < 16)
if (*depth < m_reachableStackDepth)
{
m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(*depth + 1)));
appendDup(*depth + 1);
return;
}
else if (!canBeFreelyGenerated(_slot))
{
int deficit = static_cast<int>(*depth - 15);
int deficit = static_cast<int>(*depth - (m_reachableStackDepth - 1));
YulName varName = slotVariableName(_slot);
std::string msg =
(varName.empty() ? "Slot " + stackSlotToString(_slot, m_dialect) : "Variable " + varName.str())
+ " is " + std::to_string(*depth - 15) + " too deep in the stack " + stackToString(m_stack, m_dialect);
+ " is " + std::to_string(*depth - (m_reachableStackDepth - 1)) + " too deep in the stack " + stackToString(m_stack, m_dialect);
m_stackErrors.emplace_back(StackTooDeepError(
m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulName{},
varName,
Expand Down Expand Up @@ -388,11 +389,34 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr
[&]()
{
m_assembly.appendInstruction(evmasm::Instruction::POP);
}
},
m_reachableStackDepth
);
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
}

void OptimizedEVMCodeTransform::appendSwap(size_t _depth)
{
if (_depth <= 16)
m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(_depth)));
else
{
yulAssert(_depth <= m_reachableStackDepth);
m_assembly.appendSwapN(_depth);
}
}

void OptimizedEVMCodeTransform::appendDup(size_t _depth)
{
if (_depth <= 16)
m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(_depth)));
else
{
yulAssert(_depth <= m_reachableStackDepth);
m_assembly.appendDupN(_depth);
}
}

void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
{
// Assert that this is the first visit of the block and mark as generated.
Expand Down
4 changes: 4 additions & 0 deletions libyul/backends/evm/OptimizedEVMCodeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class OptimizedEVMCodeTransform
/// Sets the source locations to the one in @a _debugData.
void createStackLayout(langutil::DebugData::ConstPtr _debugData, Stack _targetStack);

void appendSwap(size_t _depth);
void appendDup(size_t _depth);

/// Generate code for the given block @a _block.
/// Expects the current stack layout m_stack to be a stack layout that is compatible with the
/// entry layout expected by the block.
Expand Down Expand Up @@ -116,6 +119,7 @@ class OptimizedEVMCodeTransform
std::vector<StackTooDeepError> m_stackErrors;
/// True if it simulates functions with jumps. False otherwise. True for legacy bytecode
bool m_simulateFunctionsWithJumps = true;
size_t const m_reachableStackDepth{};
};

}
36 changes: 23 additions & 13 deletions libyul/backends/evm/StackHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ inline std::string stackToString(Stack const& _stack, Dialect const& _dialect)
}


// Abstraction of stack shuffling operations. Can be defined as actual concept once we switch to C++20.
// Used as an interface for the stack shuffler below.
// The shuffle operation class is expected to internally keep track of a current stack layout (the "source layout")
// that the shuffler is supposed to shuffle to a fixed target stack layout.
Expand All @@ -65,7 +64,6 @@ inline std::string stackToString(Stack const& _stack, Dialect const& _dialect)
// in the interface below.
// Based on that information the shuffler decides which is the next optimal operation to perform on the stack
// and calls the corresponding entry point in the shuffling operations (swap, pushOrDupTarget or pop).
/*
template<typename ShuffleOperations>
concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceOffset, size_t targetOffset, size_t depth) {
// Returns true, iff the current slot at sourceOffset in source layout is a suitable slot at targetOffset.
Expand Down Expand Up @@ -98,11 +96,13 @@ concept ShuffleOperationConcept = requires(ShuffleOperations ops, size_t sourceO
{ ops.pop() };
// Dups or pushes the slot that is supposed to end up at the given target offset.
{ ops.pushOrDupTarget(targetOffset) };
// Maximum reachable depth with swaps and dups.
{ ops.reachableStackDepth } -> std::convertible_to<size_t>;
};
*/

/// Helper class that can perform shuffling of a source stack layout to a target stack layout via
/// abstracted shuffle operations.
template</*ShuffleOperationConcept*/ typename ShuffleOperations>
template<ShuffleOperationConcept ShuffleOperations>
class Shuffler
{
public:
Expand All @@ -128,10 +128,10 @@ class Shuffler
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.reachableStackDepth - 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.reachableStackDepth - 1)))
{
// This slot needs to be moved.
if (!_ops.isCompatible(sourceOffset, sourceOffset))
Expand Down Expand Up @@ -256,10 +256,10 @@ class Shuffler
)
{
// We cannot swap that deep.
if (ops.sourceSize() - offset - 1 > 16)
if (ops.sourceSize() - offset - 1 > ops.reachableStackDepth)
{
// 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.reachableStackDepth + 1u) | ranges::views::reverse)
if (ops.sourceMultiplicity(ops.sourceSize() - 1 - swapDepth) < 0)
{
ops.swap(swapDepth);
Expand Down Expand Up @@ -327,7 +327,7 @@ class Shuffler
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.reachableStackDepth + 1u ? size - (ops.reachableStackDepth + 1u) : 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)
Expand Down Expand Up @@ -431,7 +431,14 @@ class Multiplicity
/// 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 _reachableStackDepth
)
{
struct ShuffleOperations
{
Expand All @@ -441,18 +448,21 @@ void createStackLayout(Stack& _currentStack, Stack const& _targetStack, Swap _sw
PushOrDup pushOrDupCallback;
Pop popCallback;
Multiplicity multiplicity;
size_t reachableStackDepth;
ShuffleOperations(
Stack& _currentStack,
Stack const& _targetStack,
Swap _swap,
PushOrDup _pushOrDup,
Pop _pop
Pop _pop,
size_t _reachableStackDepth
):
currentStack(_currentStack),
targetStack(_targetStack),
swapCallback(_swap),
pushOrDupCallback(_pushOrDup),
popCallback(_pop)
popCallback(_pop),
reachableStackDepth(_reachableStackDepth)
{
for (auto const& slot: currentStack)
--multiplicity[slot];
Expand Down Expand Up @@ -499,7 +509,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, _reachableStackDepth);

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