From b8a193aaa97ac23abc690af80c6da183b78d391b Mon Sep 17 00:00:00 2001 From: rodiazet Date: Wed, 15 Jul 2026 12:49:21 +0200 Subject: [PATCH 1/4] Introduce common factory methods for dup and swap assembly items AssemblyItem::dup()/swap() create the stack manipulation items from a depth argument, and AbstractAssembly::appendDup()/appendSwap() expose them uniformly to code generators. All call sites that constructed DUP1-16/SWAP1-16 from a computed depth are converted. No functional change. Co-authored-by: Francisco Giordano --- libevmasm/AssemblyItem.h | 13 +++++++ libevmasm/CommonSubexpressionEliminator.cpp | 4 +-- libsolidity/codegen/ArrayUtils.cpp | 20 +++++------ libsolidity/codegen/CompilerContext.cpp | 4 +-- libsolidity/codegen/CompilerUtils.cpp | 20 +++++------ libsolidity/codegen/ContractCompiler.cpp | 12 +++---- libsolidity/codegen/ExpressionCompiler.cpp | 36 +++++++++---------- libsolidity/codegen/LValue.cpp | 6 ++-- libsolidity/interface/GasEstimator.cpp | 2 +- libyul/backends/evm/AbstractAssembly.h | 4 +++ libyul/backends/evm/EVMCodeTransform.cpp | 8 ++--- libyul/backends/evm/EthAssemblyAdapter.cpp | 10 ++++++ libyul/backends/evm/EthAssemblyAdapter.h | 2 ++ libyul/backends/evm/NoOutputAssembly.cpp | 9 +++++ libyul/backends/evm/NoOutputAssembly.h | 2 ++ .../evm/OptimizedEVMCodeTransform.cpp | 8 ++--- 16 files changed, 100 insertions(+), 60 deletions(-) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 590eccb6df04..776b488ab5c3 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -102,6 +102,19 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} + /// @returns an item swapping the top of the stack with the value at @a _depth using SWAP1-16. + static AssemblyItem swap(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + // Depths outside the range [1, 16] are rejected by an assert in swapInstruction(). + return AssemblyItem(swapInstruction(static_cast(_depth)), std::move(_debugData)); + } + /// @returns an item duplicating the value at stack depth @a _depth to the top of the stack using DUP1-16. + static AssemblyItem dup(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + // Depths outside the range [1, 16] are rejected by an assert in dupInstruction(). + return AssemblyItem(dupInstruction(static_cast(_depth)), std::move(_debugData)); + } + AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; AssemblyItem& operator=(AssemblyItem const&) = default; diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index c42cc77b7666..7877af65aceb 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -478,7 +478,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, langutil::DebugData::ConstPt int instructionNum = 1 + m_stackHeight - _fromPosition; assertThrow(instructionNum <= reachableStackDepth, StackTooDeepException, util::stackTooDeepString); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); - appendItem(AssemblyItem(dupInstruction(static_cast(instructionNum)), std::move(_debugData))); + appendItem(AssemblyItem::dup(static_cast(instructionNum), std::move(_debugData))); m_stack[m_stackHeight] = m_stack[_fromPosition]; m_classPositions[m_stack[m_stackHeight]].insert(m_stackHeight); } @@ -492,7 +492,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, langutil::DebugData int instructionNum = m_stackHeight - _fromPosition; assertThrow(instructionNum <= reachableStackDepth, StackTooDeepException, util::stackTooDeepString); assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); - appendItem(AssemblyItem(swapInstruction(static_cast(instructionNum)), std::move(_debugData))); + appendItem(AssemblyItem::swap(static_cast(instructionNum), std::move(_debugData))); if (m_stack[m_stackHeight] != m_stack[_fromPosition]) { diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 80ee9553c0ae..d07a6f2d80cc 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -55,7 +55,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons bool haveSourceLengthOnStack = fromCalldata && _sourceType.isDynamicallySized(); for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); + m_context << AssemblyItem::swap(i); // stack: target_ref source_ref [source_length] if (_sourceType.baseType()->category() == Type::Category::Array) @@ -318,7 +318,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord } } // check for loop condition - m_context << Instruction::DUP1 << dupInstruction(haveByteOffset ? 5 : 4); + m_context << Instruction::DUP1 << AssemblyItem::dup(haveByteOffset ? 5 : 4); m_context << Instruction::GT; m_context.appendConditionalJumpTo(loopStart); // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset @@ -666,7 +666,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept m_context << _arrayType.length(); else { - m_context << dupInstruction(1 + _stackDepth); + m_context << AssemblyItem::dup(1 + _stackDepth); switch (_arrayType.location()) { case DataLocation::CallData: @@ -842,19 +842,19 @@ void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPos // byteOffset = 0; // } if (_byteOffsetPosition > 1) - m_context << swapInstruction(_byteOffsetPosition - 1); + m_context << AssemblyItem::swap(_byteOffsetPosition - 1); m_context << u256(_byteSize) << Instruction::ADD; if (_byteOffsetPosition > 1) - m_context << swapInstruction(_byteOffsetPosition - 1); + m_context << AssemblyItem::swap(_byteOffsetPosition - 1); // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 m_context - << u256(32) << dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) + << u256(32) << AssemblyItem::dup(1 + _byteOffsetPosition) << u256(_byteSize - 1) << Instruction::ADD << Instruction::DIV; // increment storage offset if X == 1 (just add X to it) // stack: X m_context - << swapInstruction(_storageOffsetPosition) << dupInstruction(_storageOffsetPosition + 1) - << Instruction::ADD << swapInstruction(_storageOffsetPosition); + << AssemblyItem::swap(_storageOffsetPosition) << AssemblyItem::dup(_storageOffsetPosition + 1) + << Instruction::ADD << AssemblyItem::swap(_storageOffsetPosition); // stack: X // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) m_context << u256(1) << Instruction::SUB; @@ -863,6 +863,6 @@ void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPos m_context << Instruction::MUL; else m_context - << dupInstruction(_byteOffsetPosition + 1) << Instruction::MUL - << swapInstruction(_byteOffsetPosition) << Instruction::POP; + << AssemblyItem::dup(_byteOffsetPosition + 1) << Instruction::MUL + << AssemblyItem::swap(_byteOffsetPosition) << Instruction::POP; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index d65131cc84c2..941aaeda77dc 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -425,10 +425,10 @@ void CompilerContext::appendInlineAssembly( util::errinfo_comment(util::stackTooDeepString) ); if (_context == yul::IdentifierContext::RValue) - _assembly.appendInstruction(dupInstruction(static_cast(stackDiff))); + _assembly.appendDup(stackDiff); else { - _assembly.appendInstruction(swapInstruction(static_cast(stackDiff))); + _assembly.appendSwap(stackDiff); _assembly.appendInstruction(Instruction::POP); } }; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index c7c219cb238e..9152873cad60 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -539,9 +539,9 @@ void CompilerUtils::encodeToMemory( StackTooDeepError, util::stackTooDeepString ); - m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; + m_context << AssemblyItem::dup(2 + dynPointers) << Instruction::DUP2; m_context << Instruction::SUB; - m_context << dupInstruction(2 + dynPointers - thisDynPointer); + m_context << AssemblyItem::dup(2 + dynPointers - thisDynPointer); m_context << Instruction::MSTORE; // stack: ... if (_givenTypes[i]->category() == Type::Category::StringLiteral) @@ -582,13 +582,13 @@ void CompilerUtils::encodeToMemory( copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType->sizeOnStack()); // stack: ... // copy length to memory - m_context << dupInstruction(1 + arrayType->sizeOnStack()); + m_context << AssemblyItem::dup(1 + arrayType->sizeOnStack()); ArrayUtils(m_context).retrieveLength(*arrayType, 1); // stack: ... storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... // copy the new memory pointer - m_context << swapInstruction(arrayType->sizeOnStack() + 1) << Instruction::POP; + m_context << AssemblyItem::swap(arrayType->sizeOnStack() + 1) << Instruction::POP; // stack: ... // copy data part ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); @@ -601,7 +601,7 @@ void CompilerUtils::encodeToMemory( } // remove unneeded stack elements (and retain memory pointer) - m_context << swapInstruction(argSize + dynPointers + 1); + m_context << AssemblyItem::swap(argSize + dynPointers + 1); popStackSlots(argSize + dynPointers + 1); } @@ -1278,7 +1278,7 @@ void CompilerUtils::convertType( // Move it back into its place. for (unsigned j = 0; j < std::min(sourceSize, targetSize); ++j) m_context << - swapInstruction(depth + targetSize - sourceSize) << + AssemblyItem::swap(depth + targetSize - sourceSize) << Instruction::POP; // Value shrank for (unsigned j = targetSize; j < sourceSize; ++j) @@ -1430,7 +1430,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) util::errinfo_comment(util::stackTooDeepString) ); for (unsigned i = 0; i < size; ++i) - m_context << swapInstruction(stackPosition - size + 1) << Instruction::POP; + m_context << AssemblyItem::swap(stackPosition - size + 1) << Instruction::POP; } void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) @@ -1441,7 +1441,7 @@ void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) util::stackTooDeepString ); for (unsigned i = 0; i < _itemSize; ++i) - m_context << dupInstruction(_stackDepth); + m_context << AssemblyItem::dup(_stackDepth); } void CompilerUtils::moveToStackTop(unsigned _stackDepth, unsigned _itemSize) @@ -1467,7 +1467,7 @@ void CompilerUtils::rotateStackUp(unsigned _items) util::stackTooDeepString ); for (unsigned i = 1; i < _items; ++i) - m_context << swapInstruction(_items - i); + m_context << AssemblyItem::swap(_items - i); } void CompilerUtils::rotateStackDown(unsigned _items) @@ -1478,7 +1478,7 @@ void CompilerUtils::rotateStackDown(unsigned _items) util::stackTooDeepString ); for (unsigned i = 1; i < _items; ++i) - m_context << swapInstruction(i); + m_context << AssemblyItem::swap(i); } void CompilerUtils::popStackElement(Type const& _type) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 88f05834935f..c10eb4c20fba 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -365,7 +365,7 @@ void ContractCompiler::appendInternalSelector( { size_t pivotIndex = _ids.size() / 2; FixedHash<4> pivot{_ids.at(pivotIndex)}; - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; + m_context << AssemblyItem::dup(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; evmasm::AssemblyItem lessTag{m_context.appendConditionalJump()}; // Here, we have funid >= pivot std::vector> larger{_ids.begin() + static_cast(pivotIndex), _ids.end()}; @@ -379,7 +379,7 @@ void ContractCompiler::appendInternalSelector( { for (auto const& id: _ids) { - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; + m_context << AssemblyItem::dup(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; m_context.appendConditionalJumpTo(_entryPoints.at(id)); } m_context.appendJumpTo(_notFoundTag); @@ -518,7 +518,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac { // If the function is not a view function and is called without DELEGATECALL, // we revert. - m_context << dupInstruction(2); + m_context << AssemblyItem::dup(2); m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL"); } m_context.setStackOffset(0); @@ -686,7 +686,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) } else { - m_context << swapInstruction(static_cast(stackLayout.size()) - static_cast(stackLayout.back()) - 1u); + m_context << AssemblyItem::swap(stackLayout.size() - static_cast(stackLayout.back()) - 1u); std::swap(stackLayout[static_cast(stackLayout.back())], stackLayout.back()); } for (size_t i = 0; i < stackLayout.size(); ++i) @@ -848,7 +848,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) errinfo_sourceLocation(_inlineAssembly.location()) << util::errinfo_comment(util::stackTooDeepString) ); - _assembly.appendInstruction(dupInstruction(stackDiff)); + _assembly.appendDup(stackDiff); } else solAssert(false, ""); @@ -922,7 +922,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) errinfo_sourceLocation(_inlineAssembly.location()) << util::errinfo_comment(util::stackTooDeepString) ); - _assembly.appendInstruction(swapInstruction(stackDiff)); + _assembly.appendSwap(stackDiff); _assembly.appendInstruction(Instruction::POP); } }; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 579f421f3611..a4ce341d7eb1 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -122,7 +122,7 @@ void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration co acceptAndConvert(*_varDecl.value(), *_varDecl.annotation().type); // append return - m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1); + m_context << AssemblyItem::dup(_varDecl.annotation().type->sizeOnStack() + 1); m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); } @@ -227,9 +227,9 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << Instruction::SWAP2 << Instruction::POP << Instruction::SWAP1; else if (paramTypes.size() >= 2) { - m_context << swapInstruction(static_cast(paramTypes.size())); + m_context << AssemblyItem::swap(static_cast(paramTypes.size())); m_context << Instruction::POP; - m_context << swapInstruction(static_cast(paramTypes.size())); + m_context << AssemblyItem::swap(static_cast(paramTypes.size())); utils().popStackSlots(paramTypes.size() - 1); } unsigned retSizeOnStack = 0; @@ -281,7 +281,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& errinfo_sourceLocation(_varDecl.location()) << util::errinfo_comment(util::stackTooDeepString) ); - m_context << dupInstruction(retSizeOnStack + 1); + m_context << AssemblyItem::dup(retSizeOnStack + 1); m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); } @@ -365,7 +365,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) ); // value [lvalue_ref] updated_value for (unsigned i = 0; i < itemSize; ++i) - m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP; + m_context << AssemblyItem::swap(itemSize + lvalueSize) << Instruction::POP; } m_currentLValue->storeValue(*_assignment.annotation().type, _assignment.location()); } @@ -489,7 +489,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) m_context << Instruction::DUP1; if (m_currentLValue->sizeOnStack() > 0) for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); + m_context << AssemblyItem::swap(i); } if (_unaryOperation.getOperator() == Token::Inc) { @@ -514,7 +514,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) // Stack for prefix: [ref...] (*ref)+-1 // Stack for postfix: *ref [ref...] (*ref)+-1 for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); + m_context << AssemblyItem::swap(i); m_currentLValue->storeValue( *_unaryOperation.annotation().type, _unaryOperation.location(), !_unaryOperation.isPrefixOperation()); @@ -770,7 +770,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.saltSet()) { - m_context << dupInstruction(2 + (function.valueSet() ? 1 : 0)); + m_context << AssemblyItem::dup(2 + (function.valueSet() ? 1 : 0)); m_context << Instruction::SWAP1; } @@ -779,7 +779,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // now: [salt], [value], [salt], size, offset if (function.valueSet()) - m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0)); + m_context << AssemblyItem::dup(3 + (function.saltSet() ? 1 : 0)); else m_context << u256(0); @@ -792,9 +792,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // now: [salt], [value], address if (function.valueSet()) - m_context << swapInstruction(1) << Instruction::POP; + m_context << AssemblyItem::swap(1) << Instruction::POP; if (function.saltSet()) - m_context << swapInstruction(1) << Instruction::POP; + m_context << AssemblyItem::swap(1) << Instruction::POP; // Check if zero (reverted) m_context << Instruction::DUP1 << Instruction::ISZERO; @@ -820,7 +820,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1u : 0u) + (function.valueSet() ? 1u : 0u); if (stackDepth > 0) - m_context << swapInstruction(stackDepth); + m_context << AssemblyItem::swap(stackDepth); if (function.gasSet()) m_context << Instruction::POP; break; @@ -1126,7 +1126,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) }; m_context << contractAddresses.at(function.kind()); for (unsigned i = function.sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); + m_context << AssemblyItem::swap(i); solAssert(!_functionCall.annotation().tryCall, ""); appendExternalFunctionCall(function, arguments, false); break; @@ -2827,7 +2827,7 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall()) { - m_context << dupInstruction(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes)); + m_context << AssemblyItem::dup(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes)); utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false); } @@ -2882,10 +2882,10 @@ void ExpressionCompiler::appendExternalFunctionCall( else if (useStaticCall) solAssert(!_functionType.valueSet(), "Value set for staticcall"); else if (_functionType.valueSet()) - m_context << dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); + m_context << AssemblyItem::dup(m_context.baseToCurrentStackOffset(valueStackPos)); else m_context << u256(0); - m_context << dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + m_context << AssemblyItem::dup(m_context.baseToCurrentStackOffset(contractStackPos)); bool existenceChecked = false; // Check the target contract exists (has code) for non-low-level calls. @@ -2909,7 +2909,7 @@ void ExpressionCompiler::appendExternalFunctionCall( } if (_functionType.gasSet()) - m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); + m_context << AssemblyItem::dup(m_context.baseToCurrentStackOffset(gasStackPos)); else if (m_context.evmVersion().canOverchargeGasForCall()) // Send all gas (requires tangerine whistle EVM) m_context << Instruction::GAS; @@ -2947,7 +2947,7 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context.appendConditionalRevert(true); } else - m_context << swapInstruction(remainsSize); + m_context << AssemblyItem::swap(remainsSize); utils().popStackSlots(remainsSize); // Only success flag is remaining on stack. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index e8f6ae61c65b..708a784b97f7 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -54,7 +54,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const ); solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch."); for (unsigned i = 0; i < m_size; ++i) - m_context << dupInstruction(stackPos + 1); + m_context << AssemblyItem::dup(stackPos + 1); } void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const @@ -68,7 +68,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo ); else if (stackDiff > 0) for (unsigned i = 0; i < m_size; ++i) - m_context << swapInstruction(stackDiff) << Instruction::POP; + m_context << AssemblyItem::swap(stackDiff) << Instruction::POP; if (!_move) retrieveValue(_location); } @@ -432,7 +432,7 @@ void GenericStorageItem::storeValue(Type const& _sourceType, langut } unsigned stackSize = sourceMemberType->sizeOnStack(); std::pair const& offsets = structType.storageOffsetsOfMember(member.name); - m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD; + m_context << AssemblyItem::dup(1 + stackSize) << offsets.first << Instruction::ADD; m_context << u256(offsets.second); // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true); diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index 6ab4b87dab06..ac12146f1757 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -98,7 +98,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( AssemblyItem invalidTag(PushTag, u256(-0x10)); state->feedItem(invalidTag, true); if (parametersSize > 0) - state->feedItem(swapInstruction(parametersSize)); + state->feedItem(AssemblyItem::swap(parametersSize)); return PathGasMeter::estimateMax(_items, m_evmVersion, _offset, state); } diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index c11400f97d6d..5c0795979255 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -72,6 +72,10 @@ class AbstractAssembly virtual void setStackHeight(int height) = 0; /// Append an EVM instruction. virtual void appendInstruction(evmasm::Instruction _instruction) = 0; + /// Append an operation swapping the top of the stack with the value at @a _depth. + virtual void appendSwap(size_t _depth) = 0; + /// Append an operation duplicating the value at stack depth @a _depth to the top of the stack. + virtual void appendDup(size_t _depth) = 0; /// Append a constant. virtual void appendConstant(u256 const& _constant) = 0; /// Append a label. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 83ec96ddcd2b..cd7bfb1fad95 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -186,7 +186,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) m_unusedStackSlots.erase(it); m_context->variableStackHeights[&var] = slot; if (size_t heightDiff = variableHeightDiff(var, varName, true)) - m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(heightDiff - 1))); + m_assembly.appendSwap(heightDiff - 1); m_assembly.appendInstruction(evmasm::Instruction::POP); break; } @@ -274,7 +274,7 @@ void CodeTransform::operator()(Identifier const& _identifier) // TODO: opportunity for optimization: Do not DUP if this is the last reference // to the top most element of the stack if (size_t heightDiff = variableHeightDiff(_var, _identifier.name, false)) - m_assembly.appendInstruction(evmasm::dupInstruction(static_cast(heightDiff))); + m_assembly.appendDup(heightDiff); else // Store something to balance the stack m_assembly.appendConstant(u256(0)); @@ -480,7 +480,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } else { - m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(stackLayout.size()) - static_cast(stackLayout.back()) - 1u)); + m_assembly.appendSwap(stackLayout.size() - static_cast(stackLayout.back()) - 1); std::swap(stackLayout[static_cast(stackLayout.back())], stackLayout.back()); } for (size_t i = 0; i < stackLayout.size(); ++i) @@ -760,7 +760,7 @@ void CodeTransform::generateAssignment(Identifier const& _variableName) { Scope::Variable const& _var = std::get(*var); if (size_t heightDiff = variableHeightDiff(_var, _variableName.name, true)) - m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(heightDiff - 1))); + m_assembly.appendSwap(heightDiff - 1); m_assembly.appendInstruction(evmasm::Instruction::POP); decreaseReference(_variableName.name, _var); } diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index ab693d06d5ab..207bdb0df178 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -63,6 +63,16 @@ void EthAssemblyAdapter::appendInstruction(evmasm::Instruction _instruction) m_assembly.append(_instruction); } +void EthAssemblyAdapter::appendSwap(size_t _depth) +{ + m_assembly.append(evmasm::AssemblyItem::swap(_depth)); +} + +void EthAssemblyAdapter::appendDup(size_t _depth) +{ + m_assembly.append(evmasm::AssemblyItem::dup(_depth)); +} + void EthAssemblyAdapter::appendConstant(u256 const& _constant) { m_assembly.append(_constant); diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index b92a8cb45e1d..eac0d5b35732 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -44,6 +44,8 @@ class EthAssemblyAdapter: public AbstractAssembly int stackHeight() const override; void setStackHeight(int height) override; void appendInstruction(evmasm::Instruction _instruction) override; + void appendSwap(size_t _depth) override; + void appendDup(size_t _depth) override; void appendConstant(u256 const& _constant) override; void appendLabel(LabelID _labelId) override; void appendLabelReference(LabelID _labelId) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index a05b3a96f711..a08049dd0678 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -57,6 +57,15 @@ void NoOutputAssembly::appendInstruction(evmasm::Instruction _instr) m_stackHeight += instructionInfo(_instr, m_evmVersion).ret - instructionInfo(_instr, m_evmVersion).args; } +void NoOutputAssembly::appendSwap(size_t) +{ +} + +void NoOutputAssembly::appendDup(size_t) +{ + m_stackHeight++; +} + void NoOutputAssembly::appendConstant(u256 const&) { appendInstruction(evmasm::pushInstruction(1)); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index aeb8087d936b..8a0fd1fda368 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -53,6 +53,8 @@ class NoOutputAssembly: public AbstractAssembly int stackHeight() const override { return m_stackHeight; } void setStackHeight(int height) override { m_stackHeight = height; } void appendInstruction(evmasm::Instruction _instruction) override; + void appendSwap(size_t _depth) override; + void appendDup(size_t _depth) override; void appendConstant(u256 const& _constant) override; void appendLabel(LabelID _labelId) override; void appendLabelReference(LabelID _labelId) override; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index 5fbe57fe76cb..9c4a28a51efa 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -374,16 +374,16 @@ void OptimizedEVMCodeTransform::createStackLayout(langutil::DebugData::ConstPtr void OptimizedEVMCodeTransform::appendSwap(size_t _depth) { - if (_depth <= 16) - m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(_depth))); + if (_depth <= m_reachableStackDepth) + m_assembly.appendSwap(_depth); else yulAssert(false, "Unreachable stack depth"); } void OptimizedEVMCodeTransform::appendDup(size_t _depth) { - if (_depth <= 16) - m_assembly.appendInstruction(evmasm::dupInstruction(static_cast(_depth))); + if (_depth <= m_reachableStackDepth) + m_assembly.appendDup(_depth); else yulAssert(false, "Unreachable stack depth"); } From e096436b930993b4fe2062fc71d8366e6d6b70bc Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 16 Jul 2026 11:23:38 +0200 Subject: [PATCH 2/4] Implement EIP-8024 Introduce the DUPN and SWAPN instructions with the devnet-3 immediate encoding, available in legacy bytecode starting from the "amsterdam" EVM version. The AssemblyItem::dup()/swap() factories now emit them for depths above 16, extending the reachable stack depth to 235 in legacy codegen, inline assembly and the Yul code transforms. Includes assembler, disassembler, optimizer and gas metering support as well as assembly JSON import/export. Co-authored-by: Francisco Giordano --- docs/using-the-compiler.rst | 1 + docs/yul.rst | 4 +- libevmasm/Assembly.cpp | 40 ++++++++++++++++++- libevmasm/AssemblyItem.cpp | 21 ++++++++++ libevmasm/AssemblyItem.h | 33 +++++++++++---- libevmasm/Disassemble.cpp | 34 ++++++++++++++++ libevmasm/GasMeter.cpp | 15 +++++-- libevmasm/Instruction.cpp | 2 + libevmasm/Instruction.h | 3 ++ libevmasm/KnownState.cpp | 2 +- libevmasm/SemanticInformation.cpp | 12 +++++- liblangutil/EVMVersion.cpp | 3 ++ liblangutil/EVMVersion.h | 3 +- libyul/backends/evm/AbstractAssembly.h | 2 + libyul/backends/evm/EVMDialect.cpp | 11 ++++- .../EVMInstructionInterpreter.cpp | 2 + 16 files changed, 170 insertions(+), 18 deletions(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 04ad54a99aef..294d5d1a6771 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -185,6 +185,7 @@ at each version. Backward compatibility is not guaranteed between each version. - ``osaka`` (**default**) - ``clz`` builtin function is available in inline assembly. (`EIP-7939 `_) - ``amsterdam`` (**experimental**) + - Makes ``swapn``/``dupn`` available and used to prevent stack too deep errors. .. index:: ! standard JSON, ! --standard-json .. _compiler-api: diff --git a/docs/yul.rst b/docs/yul.rst index 5c8086b00120..6fd564e39f13 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -752,8 +752,8 @@ This document does not want to be a full description of the Ethereum virtual mac Please refer to a different document if you are interested in the precise semantics. Opcodes marked with ``-`` do not return a result and all others return exactly one value. -Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I``, ``L``, ``P``, ``N`` and ``O`` are present since -Frontier, Homestead, Byzantium, Constantinople, Istanbul, London, Paris, Cancun or Osaka respectively. +Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I``, ``L``, ``P``, ``N``, ``O`` and ``A`` are present since +Frontier, Homestead, Byzantium, Constantinople, Istanbul, London, Paris, Cancun, Osaka or Amsterdam respectively. In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to but not including position ``b``, ``storage[p]`` signifies the storage contents at slot ``p``, and diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 60cddc4ba11e..1da439eda16e 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -57,6 +57,13 @@ using namespace solidity::util; namespace { +// https://eips.ethereum.org/EIPS/eip-8024 +uint8_t encodeDupSwapNImmediate(size_t _depth) +{ + solAssert(_depth >= 17 && _depth <= 235); + return static_cast((_depth + 111) % 256); +} + /// Produces instruction location info in RAII style. When an assembly instruction is added to the bytecode, /// this class can be instantiated in that scope. It will record the current bytecode size (before addition) /// and, at destruction time, record the new bytecode size. This information is then added to an external @@ -241,7 +248,31 @@ AssemblyItem Assembly::createAssemblyItemFromJSON(Json const& _json, std::vector AssemblyItem result(0); - if (c_instructions.count(name)) + if (name == "SWAPN" || name == "DUPN") + { + solRequire( + m_evmVersion.hasDupSwapN(), + AssemblyImportException, + "Instruction '" + name + "' is only available starting from the \"amsterdam\" EVM version." + ); + solRequire( + jumpType.empty(), + AssemblyImportException, + "Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')" + ); + requireValueDefinedForInstruction(name, value); + u256 const depth{value}; + solRequire( + depth >= 17 && depth <= 235, + AssemblyImportException, + "Invalid depth for instruction '" + name + "'." + ); + if (name == "SWAPN") + result = AssemblyItem::swap(static_cast(depth)); + else + result = AssemblyItem::dup(static_cast(depth)); + } + else if (c_instructions.count(name)) { AssemblyItem item{c_instructions.at(name), langutil::DebugData::create(location)}; if (!jumpType.empty()) @@ -1098,6 +1129,13 @@ LinkerObject const& Assembly::assembleLegacy() const case Operation: ret.bytecode += assembleOperation(item); break; + case SwapN: + case DupN: + solAssert(m_evmVersion.hasDupSwapN()); + ret.bytecode.push_back(static_cast(item.instruction())); + solAssert(item.data() < std::numeric_limits::max()); + ret.bytecode.push_back(encodeDupSwapNImmediate(static_cast(item.data()))); + break; case Push: ret.bytecode += assemblePush(item); break; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 83185792fd29..ad09dc21ef36 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -104,6 +104,10 @@ std::pair AssemblyItem::nameAndData(langutil::EVMVersi return {"PUSH data", toStringInHex(data())}; case VerbatimBytecode: return {"VERBATIM", util::toHex(verbatimData())}; + case SwapN: + return {"SWAPN", util::toString(data())}; + case DupN: + return {"DUPN", util::toString(data())}; case UndefinedItem: solAssert(false); } @@ -165,6 +169,9 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ } case VerbatimBytecode: return std::get<2>(*m_verbatimBytecode).size(); + case SwapN: + case DupN: + return 1 + 1; // Instruction + one byte of immediate data. According to https://eips.ethereum.org/EIPS/eip-8024 case UndefinedItem: solAssert(false); } @@ -205,8 +212,10 @@ size_t AssemblyItem::returnValues() const case PushLibraryAddress: case PushImmutable: case PushDeployTimeAddress: + case DupN: return 1; case Tag: + case SwapN: return 0; case VerbatimBytecode: return std::get<1>(*m_verbatimBytecode); @@ -240,6 +249,8 @@ bool AssemblyItem::canBeFunctional() const case AssignImmutable: case VerbatimBytecode: case UndefinedItem: + case SwapN: + case DupN: break; } return false; @@ -335,6 +346,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case VerbatimBytecode: text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode)); break; + case SwapN: + text = "swapn{" + std::to_string(static_cast(data())) + "}"; + break; + case DupN: + text = "dupn{" + std::to_string(static_cast(data())) + "}"; + break; } if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) { @@ -357,6 +374,10 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) _out << "\t" << _item.getJumpTypeAsString(); break; + case SwapN: + case DupN: + _out << " " << instructionInfo(_item.instruction(), EVMVersion()).name << " " << std::dec << _item.data(); + break; case Push: _out << " PUSH " << std::hex << _item.data() << std::dec; break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 776b488ab5c3..8b327b3c24aa 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -56,6 +56,8 @@ enum AssemblyItemType PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer. PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor. AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code. + SwapN, ///< SWAPN with immediate argument. + DupN, ///< DUPN with immediate argument. VerbatimBytecode, ///< Contains data that is inserted into the bytecode code section without modification. }; @@ -78,7 +80,10 @@ class AssemblyItem m_type(Operation), m_instruction(_i), m_debugData(std::move(_debugData)) - {} + { + solAssert(_i != Instruction::SWAPN, "Construct via AssemblyItem::swap"); + solAssert(_i != Instruction::DUPN, "Construct via AssemblyItem::dup"); + } AssemblyItem(AssemblyItemType _type, u256 _data = 0, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()): m_type(_type), m_debugData(std::move(_debugData)) @@ -102,17 +107,27 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} - /// @returns an item swapping the top of the stack with the value at @a _depth using SWAP1-16. + /// @returns an item swapping the top of the stack with the value at @a _depth, + /// using SWAP1-16 or SWAPN with an immediate argument, depending on the depth. + /// Depths above 235 cannot be encoded, see https://eips.ethereum.org/EIPS/eip-8024 static AssemblyItem swap(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) { - // Depths outside the range [1, 16] are rejected by an assert in swapInstruction(). - return AssemblyItem(swapInstruction(static_cast(_depth)), std::move(_debugData)); + if (_depth <= 16) + // Depth 0 is rejected by an assert in swapInstruction(). + return AssemblyItem(swapInstruction(static_cast(_depth)), std::move(_debugData)); + solAssert(_depth <= 235, "Invalid SWAPN depth."); + return AssemblyItem(SwapN, Instruction::SWAPN, _depth, std::move(_debugData)); } - /// @returns an item duplicating the value at stack depth @a _depth to the top of the stack using DUP1-16. + /// @returns an item duplicating the value at stack depth @a _depth to the top of the stack, + /// using DUP1-16 or DUPN with an immediate argument, depending on the depth. + /// Depths above 235 cannot be encoded, see https://eips.ethereum.org/EIPS/eip-8024 static AssemblyItem dup(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) { - // Depths outside the range [1, 16] are rejected by an assert in dupInstruction(). - return AssemblyItem(dupInstruction(static_cast(_depth)), std::move(_debugData)); + if (_depth <= 16) + // Depth 0 is rejected by an assert in dupInstruction(). + return AssemblyItem(dupInstruction(static_cast(_depth)), std::move(_debugData)); + solAssert(_depth <= 235, "Invalid DUPN depth."); + return AssemblyItem(DupN, Instruction::DUPN, _depth, std::move(_debugData)); } AssemblyItem(AssemblyItem const&) = default; @@ -151,7 +166,9 @@ class AssemblyItem bool hasInstruction() const { bool const shouldHaveInstruction = - m_type == Operation; + m_type == Operation || + m_type == SwapN || + m_type == DupN; solAssert(shouldHaveInstruction == m_instruction.has_value()); return shouldHaveInstruction; } diff --git a/libevmasm/Disassemble.cpp b/libevmasm/Disassemble.cpp index 27e860661d63..748d68d80de7 100644 --- a/libevmasm/Disassemble.cpp +++ b/libevmasm/Disassemble.cpp @@ -26,6 +26,28 @@ using namespace solidity; using namespace solidity::util; using namespace solidity::evmasm; +namespace +{ + +/// @returns true if @a _immediate encodes a valid DUPN/SWAPN stack depth. +/// The encoded depth is `immediate + 145 (mod 256)`, so immediates in [0x80, 0xff] encode +/// depths 17-144 and immediates in [0x00, 0x5a] encode depths 145-235. The remaining +/// immediates in [0x5b, 0x7f] would encode the invalid depths 236-255 and 0-16. +/// See https://eips.ethereum.org/EIPS/eip-8024 +bool isValidDupSwapNImmediate(uint8_t _immediate) +{ + return _immediate <= 0x5a || _immediate >= 0x80; +} + +/// @returns the stack depth encoded by the DUPN/SWAPN immediate argument @a _immediate, +/// i.e. `immediate + 145 (mod 256)`. See https://eips.ethereum.org/EIPS/eip-8024 +size_t decodeDupSwapNImmediate(uint8_t _immediate) +{ + solAssert(isValidDupSwapNImmediate(_immediate)); + return static_cast((_immediate + 145) % 256); +} + +} void solidity::evmasm::eachInstruction( bytes const& _mem, @@ -63,6 +85,18 @@ std::string solidity::evmasm::disassemble(bytes const& _mem, langutil::EVMVersio eachInstruction(_mem, _evmVersion, [&](Instruction _instr, u256 const& _data) { if (!isValidInstruction(_instr)) ret << "0x" << std::uppercase << std::hex << static_cast(_instr) << _delimiter; + else if (_instr == Instruction::DUPN || _instr == Instruction::SWAPN) + { + std::string const& name = instructionInfo(_instr, _evmVersion).name; + if (isValidDupSwapNImmediate(static_cast(_data))) + ret << name + << " " + << std::dec + << decodeDupSwapNImmediate(static_cast(_data)) + << _delimiter; + else + ret << "INVALID_" << name << _delimiter; + } else { InstructionInfo info = instructionInfo(_instr, _evmVersion); diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 22fabd44e2f8..4f7fc28166b6 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -59,6 +59,8 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas = runGas(Instruction::JUMPDEST, m_evmVersion); break; case Operation: + case SwapN: + case DupN: { ExpressionClasses& classes = m_state->expressionClasses(); switch (_item.instruction()) @@ -217,7 +219,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ } break; } - default: + case UndefinedItem: + case PushImmutable: + case AssignImmutable: + case VerbatimBytecode: gas = GasConsumption::infinite(); break; } @@ -308,14 +313,18 @@ unsigned GasMeter::swapGas(size_t _depth, langutil::EVMVersion _evmVersion) { if (_depth <= 16) return runGas(evmasm::swapInstruction(static_cast(_depth)), _evmVersion); - solAssert(false, "Unexpected swap instruction depth"); + 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::dupInstruction(static_cast(_depth)), _evmVersion); - solAssert(false, "Unexpected dup instruction depth"); + 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) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index cc02286c88f3..e895c47eb5d4 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -324,6 +324,8 @@ static std::map const c_instructionInfo = {Instruction::LOG2, {"LOG2", 0, 4, 0, true, Tier::Special}}, {Instruction::LOG3, {"LOG3", 0, 5, 0, true, Tier::Special}}, {Instruction::LOG4, {"LOG4", 0, 6, 0, true, Tier::Special}}, + {Instruction::SWAPN, {"SWAPN", 1, 0, 0, false, Tier::VeryLow}}, + {Instruction::DUPN, {"DUPN", 1, 0, 0, false, Tier::VeryLow}}, {Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}}, {Instruction::CALL, {"CALL", 0, 7, 1, true, Tier::Special}}, {Instruction::CALLCODE, {"CALLCODE", 0, 7, 1, true, Tier::Special}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index d5ebfa31d927..7808e52ad206 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -184,6 +184,9 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. + DUPN = 0xe6, ///< copies a value at the stack depth given as immediate argument to the top of the stack + SWAPN = 0xe7, ///< swaps the highest value with a value at a stack depth given as immediate argument + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index 94690bb129de..266bd91cf235 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -118,7 +118,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool m_expressionClasses->newClass(_item.debugData()) ); } - else if (_item.type() != Operation) + else if (_item.type() != Operation && _item.type() != SwapN && _item.type() != DupN) { solAssert(_item.deposit() == 1); if (_item.pushedValue()) diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 5f5a2279b652..38c037dd94d3 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -197,7 +197,6 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool { switch (_item.type()) { - default: case UndefinedItem: case Tag: case PushDeployTimeAddress: @@ -214,6 +213,8 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool case PushImmutable: return false; case evmasm::Operation: + case evmasm::SwapN: + case evmasm::DupN: { if (isSwapInstruction(_item) || isDupInstruction(_item)) return false; @@ -239,6 +240,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool return info.sideEffects || info.args > 2; } } + util::unreachable(); } bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) @@ -261,6 +263,8 @@ bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) { + if (_item.type() == evmasm::DupN) + return true; if (_item.type() != evmasm::Operation) return false; auto inst = _item.instruction(); @@ -269,6 +273,8 @@ bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) { + if (_item.type() == evmasm::SwapN) + return true; if (_item.type() != evmasm::Operation) return false; auto inst = _item.instruction(); @@ -334,6 +340,8 @@ bool SemanticInformation::reverts(Instruction _instruction) size_t SemanticInformation::getDupNumber(AssemblyItem const& _item) { assertThrow(isDupInstruction(_item), OptimizerException, "Not a DUP instruction."); + if (_item.type() == evmasm::DupN) + return static_cast(_item.data()); auto inst = _item.instruction(); return static_cast(inst) - static_cast(Instruction::DUP1) + 1; } @@ -341,6 +349,8 @@ size_t SemanticInformation::getDupNumber(AssemblyItem const& _item) size_t SemanticInformation::getSwapNumber(AssemblyItem const& _item) { assertThrow(isSwapInstruction(_item), OptimizerException, "Not a swap instruction."); + if (_item.type() == evmasm::SwapN) + return static_cast(_item.data()); auto inst = _item.instruction(); return static_cast(inst) - static_cast(Instruction::SWAP1) + 1; } diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index 84a3d479c305..76371a9c8601 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -60,6 +60,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const case Instruction::TSTORE: case Instruction::TLOAD: return supportsTransientStorage(); + case Instruction::DUPN: + case Instruction::SWAPN: + return hasDupSwapN(); default: return true; } diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index 1b8a3dc46d1f..90f95939e1b1 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -143,7 +143,8 @@ class EVMVersion bool hasBlobHash() const { return *this >= cancun(); } bool hasMcopy() const { return *this >= cancun(); } bool supportsTransientStorage() const { return *this >= cancun(); } - constexpr size_t reachableStackDepth() const { return 16; } + constexpr bool hasDupSwapN() const { return *this >= amsterdam(); } + constexpr size_t reachableStackDepth() const { return hasDupSwapN() ? 235 : 16; } bool hasOpcode(evmasm::Instruction _opcode) const; diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 5c0795979255..67ef046ecda9 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -73,8 +73,10 @@ class AbstractAssembly /// Append an EVM instruction. virtual void appendInstruction(evmasm::Instruction _instruction) = 0; /// Append an operation swapping the top of the stack with the value at @a _depth. + /// Chooses between SWAP1-16 and SWAPN with an immediate argument, depending on the depth. virtual void appendSwap(size_t _depth) = 0; /// Append an operation duplicating the value at stack depth @a _depth to the top of the stack. + /// Chooses between DUP1-16 and DUPN with an immediate argument, depending on the depth. virtual void appendDup(size_t _depth) = 0; /// Append a constant. virtual void appendConstant(u256 const& _constant) = 0; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 61ee043352f4..3047d4f7a40d 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -131,6 +131,14 @@ std::set> createReservedIdentifiers(langutil::EVMVersio { return _instr == evmasm::Instruction::CLZ && !_evmVersion.hasCLZ(); }; + // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the names + // swapn or dupn for VMs before amsterdam. + auto swapnDupnException = [&](evmasm::Instruction _instr) -> bool + { + return + !_evmVersion.hasDupSwapN() && + (_instr == evmasm::Instruction::SWAPN || _instr == evmasm::Instruction::DUPN); + }; std::set> reserved; for (auto const& instr: evmasm::c_instructions) @@ -143,7 +151,8 @@ std::set> createReservedIdentifiers(langutil::EVMVersio !blobBaseFeeException(instr.second) && !mcopyException(instr.second) && !transientStorageException(instr.second) && - !clzException(instr.second) + !clzException(instr.second) && + !swapnDupnException(instr.second) ) reserved.emplace(name); } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 16247037b906..be3eea1635a9 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -488,6 +488,8 @@ u256 EVMInstructionInterpreter::eval( case Instruction::SWAP14: case Instruction::SWAP15: case Instruction::SWAP16: + case Instruction::DUPN: + case Instruction::SWAPN: yulAssert(false, "Impossible in strict assembly."); } From 4e589aecc8d355cd576d04fd07bc5471e17f0d84 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 16 Jul 2026 11:23:38 +0200 Subject: [PATCH 3/4] Update tests for EIP-8024 Add assembler, disassembler, semantic and code transform tests for DUPN and SWAPN. Tests whose expectations depend on the stack being limited to 16 reachable slots are restricted to EVM versions before "amsterdam" via the new maxEVMVersionCheck() helper or the EVMVersion setting, with counterparts exceeding the new limit of 235 where applicable. Co-authored-by: Francisco Giordano --- test/Common.cpp | 7 + test/Common.h | 5 + test/libevmasm/Assembler.cpp | 67 +++++ test/libevmasm/PlainAssemblyParser.cpp | 11 + .../isoltestTesting/eip8024.asm | 7 + .../various/stack_too_deep_dupn_swapn.sol | 30 ++ ...ep_dupn_swapn_many_function_parameters.sol | 33 ++ test/libyul/CompilabilityChecker.cpp | 88 +++++- .../dupn_swapn_deep_variable_access.yul | 101 +++++++ ...n_swapn_deep_variable_access_stack_opt.yul | 282 ++++++++++++++++++ .../stackLimitEvader/cycle.yul | 1 + .../stackLimitEvader/cycle_after.yul | 1 + .../stackLimitEvader/cycle_after_2.yul | 1 + .../stackLimitEvader/cycle_before.yul | 1 + .../stackLimitEvader/cycle_before_2.yul | 1 + .../stackLimitEvader/cycle_before_after.yul | 1 + .../stackLimitEvader/function_arg.yul | 1 + .../stackLimitEvader/intersecting_cycles.yul | 1 + .../stackLimitEvader/stub.yul | 1 + .../stackLimitEvader/too_many_args_14.yul | 1 + .../stackLimitEvader/too_many_args_15.yul | 1 + .../stackLimitEvader/too_many_args_16.yul | 1 + .../stackLimitEvader/too_many_returns_15.yul | 1 + .../stackLimitEvader/too_many_returns_16.yul | 1 + .../stackLimitEvader/tree.yul | 1 + .../verbatim_many_arguments.yul | 1 + .../verbatim_many_arguments_and_returns.yul | 1 + .../verbatim_many_returns.yul | 1 + 28 files changed, 639 insertions(+), 10 deletions(-) create mode 100644 test/libevmasm/evmAssemblyTests/isoltestTesting/eip8024.asm create mode 100644 test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn.sol create mode 100644 test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn_many_function_parameters.sol create mode 100644 test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access.yul create mode 100644 test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access_stack_opt.yul diff --git a/test/Common.cpp b/test/Common.cpp index a3ea9d2f1855..6de7cf6c2a20 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -297,6 +297,13 @@ boost::unit_test::precondition::predicate_t minEVMVersionCheck(langutil::EVMVers }; } +boost::unit_test::precondition::predicate_t maxEVMVersionCheck(langutil::EVMVersion _maxEVMVersion) +{ + return [_maxEVMVersion](boost::unit_test::test_unit_id) { + return test::CommonOptions::get().evmVersion() <= _maxEVMVersion; + }; +} + bool loadVMs(CommonOptions const& _options) { if (_options.disableSemanticTests) diff --git a/test/Common.h b/test/Common.h index 082ea111ff3f..05531d1ef044 100644 --- a/test/Common.h +++ b/test/Common.h @@ -110,6 +110,11 @@ bool isValidSemanticTestPath(boost::filesystem::path const& _testPath); /// @return A predicate (function) that can be passed into @a boost::unit_test::precondition(). boost::unit_test::precondition::predicate_t minEVMVersionCheck(langutil::EVMVersion _minEVMVersion); +/// Helper that can be used to skip tests when the EVM version selected on the command line +/// is newer than @p _maxEVMVersion. +/// @return A predicate (function) that can be passed into @a boost::unit_test::precondition(). +boost::unit_test::precondition::predicate_t maxEVMVersionCheck(langutil::EVMVersion _maxEVMVersion); + bool loadVMs(CommonOptions const& _options); /** diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index 9e63668ed04c..093277ce82c4 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -56,6 +56,72 @@ namespace BOOST_AUTO_TEST_SUITE(Assembler) +BOOST_AUTO_TEST_CASE(legacy_dupn_swapn_encoding_boundaries_and_wraparound) +{ + Assembly assembly{EVMVersion::amsterdam(), false, {}}; + + assembly.append(AssemblyItem::dup(17)); + assembly.append(AssemblyItem::dup(144)); + assembly.append(AssemblyItem::dup(145)); + assembly.append(AssemblyItem::dup(235)); + assembly.append(AssemblyItem::swap(17)); + assembly.append(AssemblyItem::swap(144)); + assembly.append(AssemblyItem::swap(145)); + assembly.append(AssemblyItem::swap(235)); + + BOOST_CHECK_EQUAL(assembly.assemble().toHex(), "e680e6ffe600e65ae780e7ffe700e75a"); +} + +BOOST_AUTO_TEST_CASE(dup_swap_factories_dispatch_on_depth) +{ + BOOST_CHECK(AssemblyItem::dup(1) == Instruction::DUP1); + BOOST_CHECK(AssemblyItem::dup(16) == Instruction::DUP16); + BOOST_CHECK_EQUAL(AssemblyItem::dup(17).type(), DupN); + BOOST_CHECK_EQUAL(AssemblyItem::dup(17).data(), 17); + BOOST_CHECK_EQUAL(AssemblyItem::dup(235).type(), DupN); + BOOST_CHECK_EQUAL(AssemblyItem::dup(235).data(), 235); + BOOST_CHECK(AssemblyItem::swap(1) == Instruction::SWAP1); + BOOST_CHECK(AssemblyItem::swap(16) == Instruction::SWAP16); + BOOST_CHECK_EQUAL(AssemblyItem::swap(17).type(), SwapN); + BOOST_CHECK_EQUAL(AssemblyItem::swap(17).data(), 17); + BOOST_CHECK_EQUAL(AssemblyItem::swap(235).type(), SwapN); + BOOST_CHECK_EQUAL(AssemblyItem::swap(235).data(), 235); + + BOOST_CHECK_THROW(AssemblyItem::dup(0), InternalCompilerError); + BOOST_CHECK_THROW(AssemblyItem::dup(236), InternalCompilerError); + BOOST_CHECK_THROW(AssemblyItem::dup(256), InternalCompilerError); + BOOST_CHECK_THROW(AssemblyItem::swap(0), InternalCompilerError); + BOOST_CHECK_THROW(AssemblyItem::swap(236), InternalCompilerError); + BOOST_CHECK_THROW(AssemblyItem::swap(256), InternalCompilerError); +} + +BOOST_AUTO_TEST_CASE(legacy_dupn_swapn_rejected_before_amsterdam) +{ + auto assembleSingleItem = [](AssemblyItem const& _item) + { + Assembly assembly{EVMVersion::prague(), false, {}}; + assembly.append(_item); + assembly.assemble(); + }; + + BOOST_CHECK_THROW(assembleSingleItem(AssemblyItem::dup(20)), InternalCompilerError); + BOOST_CHECK_THROW(assembleSingleItem(AssemblyItem::swap(20)), InternalCompilerError); +} + +BOOST_AUTO_TEST_CASE(legacy_dupn_swapn_disassembly_implicit_zero_immediate) +{ + BOOST_CHECK_EQUAL(disassemble(util::fromHex("e6"), EVMVersion::amsterdam()), "DUPN 145 "); + BOOST_CHECK_EQUAL(disassemble(util::fromHex("e7"), EVMVersion::amsterdam()), "SWAPN 145 "); +} + +BOOST_AUTO_TEST_CASE(legacy_dupn_swapn_disassembly_is_version_agnostic) +{ + // Like for all other instructions, disassembly does not check whether the EVM version + // actually supports DUPN/SWAPN. + BOOST_CHECK_EQUAL(disassemble(util::fromHex("e680"), EVMVersion::prague()), "DUPN 17 "); + BOOST_CHECK_EQUAL(disassemble(util::fromHex("e780"), EVMVersion::prague()), "SWAPN 17 "); +} + BOOST_AUTO_TEST_CASE(all_assembly_items) { std::map indices = { @@ -533,6 +599,7 @@ BOOST_AUTO_TEST_CASE(can_be_functional) BOOST_CHECK(!AssemblyItem(AssignImmutable, 0).canBeFunctional()); BOOST_CHECK(!AssemblyItem(bytes{0x60, 0x00}, 0, 1).canBeFunctional()); // VerbatimBytecode BOOST_CHECK(!AssemblyItem(UndefinedItem).canBeFunctional()); + BOOST_CHECK(!AssemblyItem(UndefinedItem).canBeFunctional()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libevmasm/PlainAssemblyParser.cpp b/test/libevmasm/PlainAssemblyParser.cpp index 07b680474cb9..eb4c19c25baa 100644 --- a/test/libevmasm/PlainAssemblyParser.cpp +++ b/test/libevmasm/PlainAssemblyParser.cpp @@ -115,6 +115,17 @@ Json PlainAssemblyParser::parseAssembly(size_t _nestingLevel) codeJSON.push_back({{"name", "PUSH"}, {"value", immediateArgument}}); } } + else if (currentToken().value == "VERBATIM") + { + std::string_view verbatimData = expectArgument(); + expectNoMoreArguments(); + + if (!verbatimData.starts_with("0x")) + BOOST_THROW_EXCEPTION(std::runtime_error(formatError("The argument to VERBATIM must be a hex string prefixed with '0x'."))); + + verbatimData.remove_prefix("0x"s.size()); + codeJSON.push_back({{"name", "VERBATIM"}, {"value", verbatimData}}); + } else if (currentToken().value == "tag") { std::string_view tagID = expectArgument(); diff --git a/test/libevmasm/evmAssemblyTests/isoltestTesting/eip8024.asm b/test/libevmasm/evmAssemblyTests/isoltestTesting/eip8024.asm new file mode 100644 index 000000000000..421806dc7a6d --- /dev/null +++ b/test/libevmasm/evmAssemblyTests/isoltestTesting/eip8024.asm @@ -0,0 +1,7 @@ +VERBATIM 0xe680e7dbe6805be75be6605b +// ==== +// EVMVersion: >=amsterdam +// outputs: Bytecode,Opcodes +// ---- +// Bytecode: e680e7dbe6805be75be6605b +// Opcodes: DUPN 17 SWAPN 108 DUPN 17 JUMPDEST INVALID_SWAPN INVALID_DUPN JUMPDEST diff --git a/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn.sol b/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn.sol new file mode 100644 index 000000000000..2852530e0b0e --- /dev/null +++ b/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn.sol @@ -0,0 +1,30 @@ +contract C { + function f(uint256 a0) public pure returns (uint256 r) { + uint256 a1 = a0; + uint256 a2 = a1; + uint256 a3 = a2; + uint256 a4 = a3; + uint256 a5 = a4; + uint256 a6 = a5; + uint256 a7 = a6; + uint256 a8 = a7; + uint256 a9 = a8; + uint256 a10 = a9; + uint256 a11 = a10; + uint256 a12 = a11; + uint256 a13 = a12; + uint256 a14 = a13; + uint256 a15 = a14; + uint256 a16 = a15; + uint256 a17 = a16; + uint256 a18 = a17; + uint256 a19 = a18; + uint256 a20 = a19; + return a20; + } +} +// ==== +// EVMVersion: >=amsterdam +// compileViaYul: false +// ---- +// f(uint256): 0 -> 0 diff --git a/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn_many_function_parameters.sol b/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn_many_function_parameters.sol new file mode 100644 index 000000000000..c23dc8794668 --- /dev/null +++ b/test/libsolidity/semanticTests/various/stack_too_deep_dupn_swapn_many_function_parameters.sol @@ -0,0 +1,33 @@ +// The experimental SSA CFG code transform still assumes a reachable stack depth of 16 and does +// not support the extended depth provided by DUPN/SWAPN (EIP-8024) yet, so it is disabled here. +contract C { + function f( + uint256 a1, + uint256 a2, + uint256 a3, + uint256 a4, + uint256 a5, + uint256 a6, + uint256 a7, + uint256 a8, + uint256 a9, + uint256 a10, + uint256 a11, + uint256 a12, + uint256 a13, + uint256 a14, + uint256 a15, + uint256 a16, + uint256 a17, + uint256 a18, + uint256 a19, + uint256 a20 + ) public pure returns (uint256) { + return a1 * 1 + a2 * 2 + a3 * 3 + a4 * 4 + a5 * 5 + a6 * 6 + a7 * 7 + a8 * 8 + a9 * 9 + a10 * 10 + a11 * 11 + a12 * 12 + a13 * 13 + a14 * 14 + a15 * 15 + a16 * 16 + a17 * 17 + a18 * 18 + a19 * 19 + a20 * 20; + } +} +// ==== +// EVMVersion: >=amsterdam +// compileViaSSACFG: false +// ---- +// f(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 -> 2870 diff --git a/test/libyul/CompilabilityChecker.cpp b/test/libyul/CompilabilityChecker.cpp index a030af487eb1..2d0d07922de6 100644 --- a/test/libyul/CompilabilityChecker.cpp +++ b/test/libyul/CompilabilityChecker.cpp @@ -27,8 +27,14 @@ #include #include +#include + #include +using namespace solidity::langutil; +using namespace solidity::test; + + namespace solidity::yul::test { @@ -46,6 +52,41 @@ std::string check(std::string const& _input) out += function.first.str() + ": " + std::to_string(function.second) + " "; return out; } + +/// @returns the source of a function with @a _numVariables local variables of which the +/// first @a _numUses are used in a computation afterwards. +std::string manyVariablesSource(size_t _numVariables, size_t _numUses) +{ + soltestAssert(_numUses <= _numVariables); + + std::vector variables; + for (size_t i = 1; i <= _numVariables; ++i) + variables.emplace_back("r" + std::to_string(i)); + + std::string expression = "x"; + for (size_t i = _numUses; i >= 1; --i) + expression = "add(" + expression + ", r" + std::to_string(i) + ")"; + + return + "{ function f(a, b) -> x, y {\n" + "let " + util::joinHumanReadable(variables, ", ") + "\n" + "x := " + expression + "\n" + "} }\n"; +} + +/// @returns the source of a function with @a _numReturnVariables return variables whose +/// arguments are used in the body if @a _useArguments is set. +std::string manyReturnVariablesSource(size_t _numReturnVariables, bool _useArguments) +{ + std::vector returnVariables; + for (size_t i = 1; i <= _numReturnVariables; ++i) + returnVariables.emplace_back("r" + std::to_string(i)); + + return + "{ function f(a, b) -> " + util::joinHumanReadable(returnVariables, ", ") + " {\n" + + (_useArguments ? "r1 := 0\nsstore(a, b)\n" : "") + + "} }\n"; +} } BOOST_AUTO_TEST_SUITE(CompilabilityChecker) @@ -62,7 +103,7 @@ BOOST_AUTO_TEST_CASE(simple_function) BOOST_CHECK_EQUAL(out, ""); } -BOOST_AUTO_TEST_CASE(many_variables_few_uses) +BOOST_AUTO_TEST_CASE(many_variables_few_uses, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> x, y { @@ -90,7 +131,7 @@ BOOST_AUTO_TEST_CASE(many_variables_few_uses) BOOST_CHECK_EQUAL(out, "f: 4 "); } -BOOST_AUTO_TEST_CASE(many_variables_many_uses) +BOOST_AUTO_TEST_CASE(many_variables_many_uses, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> x, y { @@ -118,7 +159,7 @@ BOOST_AUTO_TEST_CASE(many_variables_many_uses) BOOST_CHECK_EQUAL(out, "f: 10 "); } -BOOST_AUTO_TEST_CASE(many_return_variables_unused_arguments) +BOOST_AUTO_TEST_CASE(many_return_variables_unused_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 { @@ -127,7 +168,7 @@ BOOST_AUTO_TEST_CASE(many_return_variables_unused_arguments) BOOST_CHECK_EQUAL(out, "f: 3 "); } -BOOST_AUTO_TEST_CASE(many_return_variables_used_arguments) +BOOST_AUTO_TEST_CASE(many_return_variables_used_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 { @@ -138,7 +179,34 @@ BOOST_AUTO_TEST_CASE(many_return_variables_used_arguments) BOOST_CHECK_EQUAL(out, "f: 5 "); } -BOOST_AUTO_TEST_CASE(multiple_functions_used_arguments) +// Starting from "amsterdam", DUPN and SWAPN (EIP-8024) extend the reachable stack depth to 235, +// so the deficits only appear with correspondingly more values on the stack. + +BOOST_AUTO_TEST_CASE(many_variables_few_uses_dupn_swapn, *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::amsterdam()))) +{ + BOOST_CHECK_EQUAL(check(manyVariablesSource(18, 9)), ""); + BOOST_CHECK_EQUAL(check(manyVariablesSource(237, 228)), "f: 223 "); +} + +BOOST_AUTO_TEST_CASE(many_variables_many_uses_dupn_swapn, *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::amsterdam()))) +{ + BOOST_CHECK_EQUAL(check(manyVariablesSource(18, 12)), ""); + BOOST_CHECK_EQUAL(check(manyVariablesSource(237, 231)), "f: 229 "); +} + +BOOST_AUTO_TEST_CASE(many_return_variables_unused_arguments_dupn_swapn, *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::amsterdam()))) +{ + BOOST_CHECK_EQUAL(check(manyReturnVariablesSource(19, false)), ""); + BOOST_CHECK_EQUAL(check(manyReturnVariablesSource(238, false)), "f: 3 "); +} + +BOOST_AUTO_TEST_CASE(many_return_variables_used_arguments_dupn_swapn, *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::amsterdam()))) +{ + BOOST_CHECK_EQUAL(check(manyReturnVariablesSource(19, true)), ""); + BOOST_CHECK_EQUAL(check(manyReturnVariablesSource(238, true)), "f: 5 "); +} + +BOOST_AUTO_TEST_CASE(multiple_functions_used_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 { @@ -174,7 +242,7 @@ BOOST_AUTO_TEST_CASE(multiple_functions_used_arguments) BOOST_CHECK_EQUAL(out, "h: 9 g: 5 f: 5 "); } -BOOST_AUTO_TEST_CASE(multiple_functions_unused_arguments) +BOOST_AUTO_TEST_CASE(multiple_functions_unused_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function f(a, b) -> r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19 { @@ -206,7 +274,7 @@ BOOST_AUTO_TEST_CASE(multiple_functions_unused_arguments) BOOST_CHECK_EQUAL(out, "h: 9 f: 3 "); } -BOOST_AUTO_TEST_CASE(nested_used_arguments) +BOOST_AUTO_TEST_CASE(nested_used_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function h(x) { @@ -243,7 +311,7 @@ BOOST_AUTO_TEST_CASE(nested_used_arguments) } -BOOST_AUTO_TEST_CASE(nested_unused_arguments) +BOOST_AUTO_TEST_CASE(nested_unused_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ function h(x) { @@ -276,7 +344,7 @@ BOOST_AUTO_TEST_CASE(nested_unused_arguments) } -BOOST_AUTO_TEST_CASE(also_in_outer_block_used_arguments) +BOOST_AUTO_TEST_CASE(also_in_outer_block_used_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ let x := 0 @@ -307,7 +375,7 @@ BOOST_AUTO_TEST_CASE(also_in_outer_block_used_arguments) BOOST_CHECK_EQUAL(out, "g: 5 : 9 "); } -BOOST_AUTO_TEST_CASE(also_in_outer_block_unused_arguments) +BOOST_AUTO_TEST_CASE(also_in_outer_block_unused_arguments, *boost::unit_test::precondition(maxEVMVersionCheck(EVMVersion::osaka()))) { std::string out = check(R"({ let x := 0 diff --git a/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access.yul b/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access.yul new file mode 100644 index 000000000000..4d76c74be8fe --- /dev/null +++ b/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access.yul @@ -0,0 +1,101 @@ +{ + sstore(0, f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)) + function f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) -> r + { + r := add(a1, a18) + a1 := r + } +} +// ==== +// EVMVersion: >=amsterdam +// stackOptimization: false +// ---- +// /* "":16:80 */ +// tag_2 +// /* "":77:79 */ +// 0x12 +// /* "":73:75 */ +// 0x11 +// /* "":69:71 */ +// 0x10 +// /* "":65:67 */ +// 0x0f +// /* "":61:63 */ +// 0x0e +// /* "":57:59 */ +// 0x0d +// /* "":53:55 */ +// 0x0c +// /* "":49:51 */ +// 0x0b +// /* "":45:47 */ +// 0x0a +// /* "":42:43 */ +// 0x09 +// /* "":39:40 */ +// 0x08 +// /* "":36:37 */ +// 0x07 +// /* "":33:34 */ +// 0x06 +// /* "":30:31 */ +// 0x05 +// /* "":27:28 */ +// 0x04 +// /* "":24:25 */ +// 0x03 +// /* "":21:22 */ +// 0x02 +// /* "":18:19 */ +// 0x01 +// /* "":16:80 */ +// tag_1 +// jump // in +// tag_2: +// /* "":13:14 */ +// 0x00 +// /* "":6:81 */ +// sstore +// /* "":86:236 */ +// jump(tag_3) +// tag_1: +// /* "":181:182 */ +// 0x00 +// /* "":210:213 */ +// dupn{19} +// /* "":206:208 */ +// dup3 +// /* "":202:214 */ +// add +// /* "":197:214 */ +// swap1 +// pop +// /* "":229:230 */ +// dup1 +// /* "":223:230 */ +// swap2 +// pop +// /* "":86:236 */ +// tag_4: +// swapn{19} +// swapn{18} +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// pop +// jump // out +// tag_3: diff --git a/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access_stack_opt.yul b/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access_stack_opt.yul new file mode 100644 index 000000000000..51df66cb7191 --- /dev/null +++ b/test/libyul/evmCodeTransform/dupn_swapn_deep_variable_access_stack_opt.yul @@ -0,0 +1,282 @@ +{ + let a1 := calldataload(0x0) + let a2 := calldataload(0x20) + let a3 := calldataload(0x40) + let a4 := calldataload(0x60) + let a5 := calldataload(0x80) + let a6 := calldataload(0xa0) + let a7 := calldataload(0xc0) + let a8 := calldataload(0xe0) + let a9 := calldataload(0x100) + let a10 := calldataload(0x120) + let a11 := calldataload(0x140) + let a12 := calldataload(0x160) + let a13 := calldataload(0x180) + let a14 := calldataload(0x1a0) + let a15 := calldataload(0x1c0) + let a16 := calldataload(0x1e0) + let a17 := calldataload(0x200) + let a18 := calldataload(0x220) + let a19 := calldataload(0x240) + let a20 := calldataload(0x260) + let a21 := calldataload(0x280) + let a22 := calldataload(0x2a0) + let a23 := calldataload(0x2c0) + let a24 := calldataload(0x2e0) + sstore(1, a1) + sstore(2, a2) + sstore(3, a3) + sstore(4, a4) + sstore(5, a5) + sstore(6, a6) + sstore(7, a7) + sstore(8, a8) + sstore(9, a9) + sstore(10, a10) + sstore(11, a11) + sstore(12, a12) + sstore(13, a13) + sstore(14, a14) + sstore(15, a15) + sstore(16, a16) + sstore(17, a17) + sstore(18, a18) + sstore(19, a19) + sstore(20, a20) + sstore(21, a21) + sstore(22, a22) + sstore(23, a23) + sstore(24, a24) + sstore(0, add(a1, a24)) +} +// ==== +// EVMVersion: >=amsterdam +// stackOptimization: true +// ---- +// /* "":29:32 */ +// 0x00 +// /* "":16:33 */ +// calldataload +// /* "":61:65 */ +// 0x20 +// /* "":48:66 */ +// calldataload +// /* "":81:99 */ +// swap1 +// /* "":94:98 */ +// 0x40 +// /* "":81:99 */ +// calldataload +// /* "":127:131 */ +// 0x60 +// /* "":114:132 */ +// calldataload +// /* "":160:164 */ +// 0x80 +// /* "":147:165 */ +// calldataload +// /* "":193:197 */ +// 0xa0 +// /* "":180:198 */ +// calldataload +// /* "":226:230 */ +// 0xc0 +// /* "":213:231 */ +// calldataload +// /* "":259:263 */ +// 0xe0 +// /* "":246:264 */ +// calldataload +// /* "":292:297 */ +// 0x0100 +// /* "":279:298 */ +// calldataload +// /* "":327:332 */ +// 0x0120 +// /* "":314:333 */ +// calldataload +// /* "":362:367 */ +// 0x0140 +// /* "":349:368 */ +// calldataload +// /* "":397:402 */ +// 0x0160 +// /* "":384:403 */ +// calldataload +// /* "":432:437 */ +// 0x0180 +// /* "":419:438 */ +// calldataload +// /* "":467:472 */ +// 0x01a0 +// /* "":454:473 */ +// calldataload +// /* "":489:508 */ +// swap2 +// /* "":502:507 */ +// 0x01c0 +// /* "":489:508 */ +// calldataload +// /* "":524:543 */ +// swap4 +// /* "":537:542 */ +// 0x01e0 +// /* "":524:543 */ +// calldataload +// /* "":559:578 */ +// swap6 +// /* "":572:577 */ +// 0x0200 +// /* "":559:578 */ +// calldataload +// /* "":594:613 */ +// swap8 +// /* "":607:612 */ +// 0x0220 +// /* "":594:613 */ +// calldataload +// /* "":629:648 */ +// swap10 +// /* "":642:647 */ +// 0x0240 +// /* "":629:648 */ +// calldataload +// /* "":664:683 */ +// swap12 +// /* "":677:682 */ +// 0x0260 +// /* "":664:683 */ +// calldataload +// /* "":699:718 */ +// swap14 +// /* "":712:717 */ +// 0x0280 +// /* "":699:718 */ +// calldataload +// /* "":734:753 */ +// swap16 +// /* "":747:752 */ +// 0x02a0 +// /* "":734:753 */ +// calldataload +// /* "":769:788 */ +// swapn{18} +// /* "":782:787 */ +// 0x02c0 +// /* "":769:788 */ +// calldataload +// /* "":804:823 */ +// swapn{20} +// /* "":817:822 */ +// 0x02e0 +// /* "":804:823 */ +// calldataload +// /* "":828:841 */ +// swapn{23} +// dupn{23} +// /* "":835:836 */ +// 0x01 +// /* "":828:841 */ +// sstore +// /* "":853:854 */ +// 0x02 +// /* "":846:859 */ +// sstore +// /* "":871:872 */ +// 0x03 +// /* "":864:877 */ +// sstore +// /* "":889:890 */ +// 0x04 +// /* "":882:895 */ +// sstore +// /* "":907:908 */ +// 0x05 +// /* "":900:913 */ +// sstore +// /* "":925:926 */ +// 0x06 +// /* "":918:931 */ +// sstore +// /* "":943:944 */ +// 0x07 +// /* "":936:949 */ +// sstore +// /* "":961:962 */ +// 0x08 +// /* "":954:967 */ +// sstore +// /* "":979:980 */ +// 0x09 +// /* "":972:985 */ +// sstore +// /* "":997:999 */ +// 0x0a +// /* "":990:1005 */ +// sstore +// /* "":1017:1019 */ +// 0x0b +// /* "":1010:1025 */ +// sstore +// /* "":1037:1039 */ +// 0x0c +// /* "":1030:1045 */ +// sstore +// /* "":1057:1059 */ +// 0x0d +// /* "":1050:1065 */ +// sstore +// /* "":1077:1079 */ +// 0x0e +// /* "":1070:1085 */ +// sstore +// /* "":1097:1099 */ +// 0x0f +// /* "":1090:1105 */ +// sstore +// /* "":1117:1119 */ +// 0x10 +// /* "":1110:1125 */ +// sstore +// /* "":1137:1139 */ +// 0x11 +// /* "":1130:1145 */ +// sstore +// /* "":1157:1159 */ +// 0x12 +// /* "":1150:1165 */ +// sstore +// /* "":1177:1179 */ +// 0x13 +// /* "":1170:1185 */ +// sstore +// /* "":1197:1199 */ +// 0x14 +// /* "":1190:1205 */ +// sstore +// /* "":1217:1219 */ +// 0x15 +// /* "":1210:1225 */ +// sstore +// /* "":1237:1239 */ +// 0x16 +// /* "":1230:1245 */ +// sstore +// /* "":1257:1259 */ +// 0x17 +// /* "":1250:1265 */ +// sstore +// /* "":1270:1285 */ +// dup2 +// /* "":1277:1279 */ +// 0x18 +// /* "":1270:1285 */ +// sstore +// /* "":1300:1312 */ +// add +// /* "":1297:1298 */ +// 0x00 +// /* "":1290:1313 */ +// sstore +// /* "":0:1315 */ +// stop diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul index 11f35b19b7c6..3f00ae81f0ca 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul @@ -45,6 +45,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul index 4b703b556316..b3c9669378da 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul @@ -45,6 +45,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul index ca9ba91c8289..717762edef08 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul @@ -48,6 +48,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul index 49923142f520..99efa5c7042a 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul @@ -50,6 +50,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul index cb7dd6f34d71..7dc25c250b48 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul @@ -53,6 +53,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul index 54730ff2ebfc..c02b74b325c3 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul @@ -54,6 +54,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul index 4b846a8077bc..3db10ffebc9d 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul @@ -41,6 +41,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul index a875fda5280c..0a190bb16c89 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul @@ -49,6 +49,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul index fff083303dfb..89bfe3dc8c51 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul @@ -46,6 +46,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul index 47644384d3d2..7422783d30d6 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul @@ -16,6 +16,7 @@ } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul index f67b1d9794cc..3276fbcbd3f2 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul @@ -18,6 +18,7 @@ } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul index 9b7e25ac56f7..5e8794b55512 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul @@ -18,6 +18,7 @@ } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul index 66d2df14e611..4c9250753384 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul @@ -14,6 +14,7 @@ } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul index 2daadcbdb3bd..6db552451068 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul @@ -14,6 +14,7 @@ } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul index 05db623de824..dac7e0ddf4e0 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul @@ -165,6 +165,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul index 12dc5cbb31a7..c3e14fdc8923 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul @@ -25,6 +25,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul index df9412078dcf..86290230ce3c 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul @@ -46,6 +46,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul index 734a5af75c6e..a4e813d85071 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul @@ -8,6 +8,7 @@ } } // ==== +// EVMVersion: <=osaka // ---- // step: stackLimitEvader // From deb1b0e898c31cf134ee9bfc5c4696d8cc117a83 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 16 Jul 2026 11:21:26 +0200 Subject: [PATCH 4/4] Support multiple EVM version constraints in test settings All space-separated constraints in the `EVMVersion` setting have to be satisfied for the test to run. This allows expressing version ranges, e.g. `>homestead <=osaka` for tests whose expectations only hold before "amsterdam" but which cannot run on the oldest versions either. --- test/TestCase.cpp | 86 ++++++++++--------- ...huffler_bring_up_target_slot_bfs_dedup.yul | 2 +- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index 8081d7d86068..9e79be51c05f 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -99,49 +99,55 @@ TestCase::TestResult TestCase::checkResult(std::ostream& _stream, const std::str void EVMVersionRestrictedTestCase::processEVMVersionSetting() { - std::string versionString = m_reader.stringSetting("EVMVersion", "any"); - if (versionString == "any") + std::string versionSetting = m_reader.stringSetting("EVMVersion", "any"); + if (versionSetting == "any") return; - std::string comparator; - size_t versionBegin = 0; - for (auto character: versionString) - if (!isalpha(character, std::locale::classic()) && character != '@') - { - comparator += character; - versionBegin++; - } + // Multiple space-separated constraints are allowed and all of them have to be satisfied. + std::istringstream versionConstraints(versionSetting); + std::string versionString; + while (versionConstraints >> versionString) + { + std::string comparator; + size_t versionBegin = 0; + for (auto character: versionString) + if (!isalpha(character, std::locale::classic()) && character != '@') + { + comparator += character; + versionBegin++; + } + else + break; + + versionString = versionString.substr(versionBegin); + std::optional version; + if (versionString == "current") + version = std::make_optional(); + else + version = langutil::EVMVersion::fromString(versionString); + if (!version) + BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM version: \"" + versionString + "\""}); + + langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); + bool comparisonResult; + if (comparator == ">") + comparisonResult = evmVersion > version; + else if (comparator == ">=") + comparisonResult = evmVersion >= version; + else if (comparator == "<") + comparisonResult = evmVersion < version; + else if (comparator == "<=") + comparisonResult = evmVersion <= version; + else if (comparator == "=") + comparisonResult = evmVersion == version; + else if (comparator == "!") + comparisonResult = !(evmVersion == version); else - break; - - versionString = versionString.substr(versionBegin); - std::optional version; - if (versionString == "current") - version = std::make_optional(); - else - version = langutil::EVMVersion::fromString(versionString); - if (!version) - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM version: \"" + versionString + "\""}); - - langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); - bool comparisonResult; - if (comparator == ">") - comparisonResult = evmVersion > version; - else if (comparator == ">=") - comparisonResult = evmVersion >= version; - else if (comparator == "<") - comparisonResult = evmVersion < version; - else if (comparator == "<=") - comparisonResult = evmVersion <= version; - else if (comparator == "=") - comparisonResult = evmVersion == version; - else if (comparator == "!") - comparisonResult = !(evmVersion == version); - else - BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM comparator: \"" + comparator + "\""}); - - if (!comparisonResult) - m_shouldRun = false; + BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid EVM comparator: \"" + comparator + "\""}); + + if (!comparisonResult) + m_shouldRun = false; + } } EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _filename): diff --git a/test/libyul/yulOptimizerTests/fullSuite/stack_shuffler_bring_up_target_slot_bfs_dedup.yul b/test/libyul/yulOptimizerTests/fullSuite/stack_shuffler_bring_up_target_slot_bfs_dedup.yul index 53d64ca980a6..40df8c8e0a0d 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/stack_shuffler_bring_up_target_slot_bfs_dedup.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/stack_shuffler_bring_up_target_slot_bfs_dedup.yul @@ -55,7 +55,7 @@ } } // ==== -// EVMVersion: >homestead +// EVMVersion: >homestead <=osaka // ---- // step: fullSuite //