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 590eccb6df04..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,6 +107,29 @@ 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 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()) + { + 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 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()) + { + 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; AssemblyItem(AssemblyItem&&) = default; AssemblyItem& operator=(AssemblyItem const&) = default; @@ -138,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/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/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/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..67ef046ecda9 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -72,6 +72,12 @@ 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. + /// 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; /// 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/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/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"); } 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/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/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/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 // 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 // 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."); }