diff --git a/Changelog.md b/Changelog.md index 19b71804a2fe..ea9cd13676c0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: * Introduce global function ``blobhash(uint)`` for retrieving versioned hashes of blobs, akin to the homonymous Yul builtin. * Yul: Introduce builtin ``blobbasefee()`` for retrieving the blob base fee of the current block. * Yul: Introduce builtin ``blobhash()`` for retrieving versioned hashes of blobs associated with the transaction. + * Yul: Introduce builtin ``mcopy()`` for cheaply copying data between memory areas. Compiler Features: * EVM: Support for the EVM Version "Cancun". diff --git a/docs/grammar/SolidityLexer.g4 b/docs/grammar/SolidityLexer.g4 index b553c3b79c17..91d6b4177434 100644 --- a/docs/grammar/SolidityLexer.g4 +++ b/docs/grammar/SolidityLexer.g4 @@ -301,7 +301,7 @@ YulEVMBuiltin: | 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'msize' | 'gas' | 'address' | 'balance' | 'selfbalance' | 'caller' | 'callvalue' | 'calldataload' | 'calldatasize' | 'calldatacopy' | 'extcodesize' | 'extcodecopy' | 'returndatasize' - | 'returndatacopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode' + | 'returndatacopy' | 'mcopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode' | 'delegatecall' | 'staticcall' | 'return' | 'revert' | 'selfdestruct' | 'invalid' | 'log0' | 'log1' | 'log2' | 'log3' | 'log4' | 'chainid' | 'origin' | 'gasprice' | 'blockhash' | 'blobhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty' diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 93bdff8661bf..bb495c8155c5 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -179,6 +179,7 @@ at each version. Backward compatibility is not guaranteed between each version. - ``cancun`` - The block's blob base fee (`EIP-7516 `_ and `EIP-4844 `_) can be accessed via the global ``block.blobbasefee`` or ``blobbasefee()`` in inline assembly. - Introduces ``blobhash()`` in inline assembly and a corresponding global function to retrieve versioned hashes of blobs associated with the transaction (see `EIP-4844 `_). + - Opcode ``mcopy`` is available in assembly (see `EIP-5656 `_). .. index:: ! standard JSON, ! --standard-json .. _compiler-api: diff --git a/docs/yul.rst b/docs/yul.rst index 2f0dc19dc8b9..482e80ea134c 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -865,6 +865,8 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a +-------------------------+-----+---+-----------------------------------------------------------------+ | returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | +-------------------------+-----+---+-----------------------------------------------------------------+ +| mcopy(t, f, s) | `-` | N | copy s bytes from mem at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ | extcodehash(a) | | C | code hash of address a | +-------------------------+-----+---+-----------------------------------------------------------------+ | create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 271a6ce2de3c..6594d1ff6c2f 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -115,6 +115,16 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas += memoryGas(0, -2); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2)); break; + case Instruction::MCOPY: + { + GasConsumption memoryGasFromRead = memoryGas(-1, -2); + GasConsumption memoryGasFromWrite = memoryGas(0, -2); + + gas = runGas(_item.instruction(), m_evmVersion); + gas += (memoryGasFromRead < memoryGasFromWrite ? memoryGasFromWrite : memoryGasFromRead); + gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2)); + break; + } case Instruction::EXTCODESIZE: gas = GasCosts::extCodeGas(m_evmVersion); break; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 611b17647e97..c00bc3542151 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -70,6 +70,7 @@ std::map const solidity::evmasm::c_instructions = { "EXTCODECOPY", Instruction::EXTCODECOPY }, { "RETURNDATASIZE", Instruction::RETURNDATASIZE }, { "RETURNDATACOPY", Instruction::RETURNDATACOPY }, + { "MCOPY", Instruction::MCOPY }, { "EXTCODEHASH", Instruction::EXTCODEHASH }, { "BLOCKHASH", Instruction::BLOCKHASH }, { "BLOBHASH", Instruction::BLOBHASH }, @@ -222,6 +223,7 @@ static std::map const c_instructionInfo = { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } }, { Instruction::RETURNDATASIZE, {"RETURNDATASIZE", 0, 0, 1, false, Tier::Base } }, { Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 0, 3, 0, true, Tier::VeryLow } }, + { Instruction::MCOPY, { "MCOPY", 0, 3, 0, true, Tier::VeryLow } }, { Instruction::EXTCODEHASH, { "EXTCODEHASH", 0, 1, 1, false, Tier::Balance } }, { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } }, { Instruction::BLOBHASH, { "BLOBHASH", 0, 1, 1, false, Tier::VeryLow } }, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index f05dcb9f63f8..4e7ac8b97ff0 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -104,6 +104,7 @@ enum class Instruction: uint8_t MSIZE, ///< get the size of active memory GAS, ///< get the amount of available gas JUMPDEST, ///< set a potential jump destination + MCOPY = 0x5e, ///< copy between memory areas PUSH0 = 0x5f, ///< place the value 0 on stack PUSH1 = 0x60, ///< place 1 byte item on stack diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 346dcb0d4d33..5c8b986be90d 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -104,6 +104,25 @@ std::vector SemanticInformation::readWriteOperat op.lengthParameter = 2; return {op}; } + case Instruction::MCOPY: + { + assertThrow(memory(_instruction) != Effect::None, OptimizerException, ""); + assertThrow(storage(_instruction) == Effect::None, OptimizerException, ""); + + Operation readOperation; + readOperation.effect = Read; + readOperation.location = Location::Memory; + readOperation.startParameter = 1; + readOperation.lengthParameter = 2; + + Operation writeOperation; + writeOperation.effect = Write; + writeOperation.location = Location::Memory; + writeOperation.startParameter = 0; + writeOperation.lengthParameter = 2; + + return {readOperation, writeOperation}; + } case Instruction::STATICCALL: case Instruction::CALL: case Instruction::CALLCODE: @@ -188,7 +207,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool )) return false; //@todo: We do not handle the following memory instructions for now: - // calldatacopy, codecopy, extcodecopy, mstore8, + // calldatacopy, codecopy, extcodecopy, mcopy, mstore8, // msize (note that msize also depends on memory read access) // the second requirement will be lifted once it is implemented @@ -363,6 +382,7 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction case Instruction::CODECOPY: case Instruction::EXTCODECOPY: case Instruction::RETURNDATACOPY: + case Instruction::MCOPY: case Instruction::MSTORE: case Instruction::MSTORE8: case Instruction::CALL: diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index b6d906bc1439..746dfa4c67d1 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -117,6 +117,7 @@ struct EVMBuiltins static auto constexpr EXTCODECOPY = PatternGenerator{}; static auto constexpr RETURNDATASIZE = PatternGenerator{}; static auto constexpr RETURNDATACOPY = PatternGenerator{}; + static auto constexpr MCOPY = PatternGenerator{}; static auto constexpr EXTCODEHASH = PatternGenerator{}; static auto constexpr BLOCKHASH = PatternGenerator{}; static auto constexpr BLOBHASH = PatternGenerator{}; diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index 6fe2bbb1f4f1..34996dc6b98c 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -53,6 +53,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const return hasBlobHash(); case Instruction::BLOBBASEFEE: return hasBlobBaseFee(); + case Instruction::MCOPY: + return hasMcopy(); default: return true; } diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index e59c5d3aa12c..5b4e73b15e7d 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -105,6 +105,7 @@ class EVMVersion: bool hasPrevRandao() const { return *this >= paris(); } bool hasPush0() const { return *this >= shanghai(); } bool hasBlobHash() const { return *this >= cancun(); } + bool hasMcopy() const { return *this >= cancun(); } bool hasOpcode(evmasm::Instruction _opcode) const; diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 834a7549a502..c4dade689830 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -736,6 +736,9 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio else if (_instr == evmasm::Instruction::BLOBHASH && !m_evmVersion.hasBlobHash()) // TODO: Change this assertion to an error, similar to the ones above, when Cancun becomes the default EVM version. yulAssert(false); + else if (_instr == evmasm::Instruction::MCOPY && !m_evmVersion.hasMcopy()) + // TODO: Change this assertion to an error, similar to the ones above, when Cancun becomes the default EVM version. + yulAssert(false); else if (_instr == evmasm::Instruction::PC) m_errorReporter.error( 2450_error, diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 6b34f60c74aa..36e6f1d0078d 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -127,6 +127,13 @@ std::set createReservedIdentifiers(langutil::EVMVersion _evmVersion) return _instr == evmasm::Instruction::BLOBBASEFEE && _evmVersion < langutil::EVMVersion::cancun(); }; + // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name + // mcopy for VMs before london. + auto mcopyException = [&](evmasm::Instruction _instr) -> bool + { + return _instr == evmasm::Instruction::MCOPY && _evmVersion < langutil::EVMVersion::cancun(); + }; + // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name // prevrandao for VMs before paris. auto prevRandaoException = [&](std::string const& _instrName) -> bool @@ -150,7 +157,8 @@ std::set createReservedIdentifiers(langutil::EVMVersion _evmVersion) !baseFeeException(instr.second) && !prevRandaoException(name) && !blobHashException(instr.second) && - !blobBaseFeeException(instr.second) + !blobBaseFeeException(instr.second) && + !mcopyException(instr.second) ) reserved.emplace(name); } diff --git a/libyul/optimiser/UnusedStoreEliminator.cpp b/libyul/optimiser/UnusedStoreEliminator.cpp index d88b07f6b158..95296369b77d 100644 --- a/libyul/optimiser/UnusedStoreEliminator.cpp +++ b/libyul/optimiser/UnusedStoreEliminator.cpp @@ -172,9 +172,12 @@ void UnusedStoreEliminator::visit(Statement const& _statement) *instruction == Instruction::CODECOPY || *instruction == Instruction::CALLDATACOPY || *instruction == Instruction::RETURNDATACOPY || + // TODO: Removing MCOPY is complicated because it's not just a store but also a load. + //*instruction == Instruction::MCOPY || *instruction == Instruction::MSTORE || *instruction == Instruction::MSTORE8; bool isCandidateForRemoval = + *instruction != Instruction::MCOPY && SemanticInformation::otherState(*instruction) != SemanticInformation::Write && ( SemanticInformation::storage(*instruction) == SemanticInformation::Write || (!m_ignoreMemory && SemanticInformation::memory(*instruction) == SemanticInformation::Write) diff --git a/scripts/test_antlr_grammar.sh b/scripts/test_antlr_grammar.sh index ea00cc0f6e0e..d21228c5be3f 100755 --- a/scripts/test_antlr_grammar.sh +++ b/scripts/test_antlr_grammar.sh @@ -123,6 +123,8 @@ done < <( grep -v -E 'inlineAssembly/basefee_berlin_function.sol' | # Skipping a test with "let blobbasefee := ..." grep -v -E 'inlineAssembly/blobbasefee_shanghai_function.sol' | + # Skipping a test with "let mcopy := ..." + grep -v -E 'inlineAssembly/mcopy_as_identifier_pre_cancun.sol' | # Skipping tests with "let prevrandao := ..." grep -v -E 'inlineAssembly/prevrandao_allowed_function_pre_paris.sol' | grep -v -E 'inlineAssembly/prevrandao_disallowed_function_post_paris.sol' | diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index a0599f552a73..1afebac5c5c8 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -32,6 +32,7 @@ using namespace solidity::langutil; using namespace solidity::evmasm; using namespace solidity::frontend; using namespace solidity::frontend::test; +using namespace solidity::test; namespace solidity::frontend::test { @@ -339,6 +340,113 @@ BOOST_AUTO_TEST_CASE(complex_control_flow) testRunTimeGas("ln(int128)", std::vector{encodeArgs(0), encodeArgs(10), encodeArgs(105), encodeArgs(30000)}); } +BOOST_AUTO_TEST_CASE( + mcopy_memory_expansion_gas, + *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::cancun())) +) +{ + char const* sourceCode = R"( + contract C { + function no_expansion() public { + assembly { + mstore(0xffe0, 1) // expand memory before using mcopy + mcopy(0, 0xffff, 1) + return(0, 1) + } + } + + function expansion_on_write() public { + assembly { + mcopy(0xffff, 0, 1) + return(0xffff, 1) + } + } + + function expansion_on_read() public { + assembly { + mcopy(0, 0xffff, 1) + return(0, 1) + } + } + + function expansion_on_read_write() public { + assembly { + mcopy(0xffff, 0xffff, 1) + return(0, 1) + } + } + + function expansion_on_zero_size() public { + assembly { + mcopy(0xffff, 0xffff, 0) + return(0, 1) + } + } + + function expansion_on_0_0_0() public { + assembly { + mcopy(0, 0, 0) + return(0, 1) + } + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("no_expansion()", {encodeArgs()}); + testRunTimeGas("expansion_on_write()", {encodeArgs()}); + testRunTimeGas("expansion_on_read()", {encodeArgs()}); + testRunTimeGas("expansion_on_read_write()", {encodeArgs()}); + testRunTimeGas("expansion_on_zero_size()", {encodeArgs()}); + testRunTimeGas("expansion_on_0_0_0()", {encodeArgs()}); +} + +BOOST_AUTO_TEST_CASE( + mcopy_word_gas, + *boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::cancun())) +) +{ + char const* sourceCode = R"( + contract C { + function no_overlap() public { + assembly { + mstore(0xffe0, 1) // expand memory before using mcopy + mcopy(0x4000, 0x2000, 0x2000) + return(0, 0x10000) + } + } + + function overlap_right() public { + assembly { + mstore(0xffe0, 1) // expand memory before using mcopy + mcopy(0x3000, 0x2000, 0x2000) + return(0, 0x10000) + } + } + + function overlap_left() public { + assembly { + mstore(0xffe0, 1) // expand memory before using mcopy + mcopy(0x1000, 0x2000, 0x2000) + return(0, 0x10000) + } + } + + function overlap_full() public { + assembly { + mstore(0xffe0, 1) // expand memory before using mcopy + mcopy(0x2000, 0x2000, 0x2000) + return(0, 0x10000) + } + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("no_overlap()", {encodeArgs()}); + testRunTimeGas("overlap_right()", {encodeArgs()}); + testRunTimeGas("overlap_left()", {encodeArgs()}); + testRunTimeGas("overlap_full()", {encodeArgs()}); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/memoryGuardTests/marked_empty.sol b/test/libsolidity/memoryGuardTests/marked_empty.sol new file mode 100644 index 000000000000..faae617a2514 --- /dev/null +++ b/test/libsolidity/memoryGuardTests/marked_empty.sol @@ -0,0 +1,12 @@ +contract C { + constructor() { + assembly ("memory-safe") {} + } + + function f() public { + assembly ("memory-safe") {} + } +} +// ---- +// :C(creation) true +// :C(runtime) true diff --git a/test/libsolidity/memoryGuardTests/marked_with_memory_access.sol b/test/libsolidity/memoryGuardTests/marked_with_memory_access.sol new file mode 100644 index 000000000000..8ceb0b1a1f1f --- /dev/null +++ b/test/libsolidity/memoryGuardTests/marked_with_memory_access.sol @@ -0,0 +1,16 @@ +contract C { + constructor() { + assembly ("memory-safe") { + mstore(0, 1) + } + } + + function store() public { + assembly ("memory-safe") { + mstore(0, 1) + } + } +} +// ---- +// :C(creation) true +// :C(runtime) true diff --git a/test/libsolidity/memoryGuardTests/unmarked_empty.sol b/test/libsolidity/memoryGuardTests/unmarked_empty.sol new file mode 100644 index 000000000000..c87e88936a3c --- /dev/null +++ b/test/libsolidity/memoryGuardTests/unmarked_empty.sol @@ -0,0 +1,12 @@ +contract C { + constructor() { + assembly {} + } + + function f() public { + assembly {} + } +} +// ---- +// :C(creation) true +// :C(runtime) true diff --git a/test/libsolidity/memoryGuardTests/unmarked_with_memory_access.sol b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access.sol deleted file mode 100644 index 88f826fdb944..000000000000 --- a/test/libsolidity/memoryGuardTests/unmarked_with_memory_access.sol +++ /dev/null @@ -1,8 +0,0 @@ -contract C { - function f() public pure { - assembly { mstore(0,0) } - } -} -// ---- -// :C(creation) true -// :C(runtime) false diff --git a/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy.sol b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy.sol new file mode 100644 index 000000000000..075cfb6ff560 --- /dev/null +++ b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy.sol @@ -0,0 +1,18 @@ +contract C { + constructor() { + assembly { + mcopy(1000, 2000, 1000) + } + } + + function copy() public { + assembly { + mcopy(1000, 2000, 1000) + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// :C(creation) false +// :C(runtime) false diff --git a/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy_zero_size.sol b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy_zero_size.sol new file mode 100644 index 000000000000..5ef77fcc6925 --- /dev/null +++ b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mcopy_zero_size.sol @@ -0,0 +1,18 @@ +contract C { + constructor() { + assembly { + mcopy(1000, 2000, 0) + } + } + + function copy() public { + assembly { + mcopy(1000, 2000, 0) + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// :C(creation) false +// :C(runtime) false diff --git a/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mstore.sol b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mstore.sol new file mode 100644 index 000000000000..3cf05a79169c --- /dev/null +++ b/test/libsolidity/memoryGuardTests/unmarked_with_memory_access_mstore.sol @@ -0,0 +1,16 @@ +contract C { + constructor() { + assembly { + mstore(0, 1) + } + } + + function store() public { + assembly { + mstore(0, 1) + } + } +} +// ---- +// :C(creation) false +// :C(runtime) false diff --git a/test/libsolidity/semanticTests/inlineAssembly/mcopy.sol b/test/libsolidity/semanticTests/inlineAssembly/mcopy.sol new file mode 100644 index 000000000000..e98409f3d4e1 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/mcopy.sol @@ -0,0 +1,14 @@ +contract C { + function f(bytes memory src) public pure returns (bytes memory dst) { + assembly { + mcopy(add(dst, 0x1f), add(src, 0x1f), 0x01) // Copy over src length byte to dst + mcopy(add(dst, 0x20), add(src, 0x00), 0x08) // Copy 8 zero bytes to dst + mcopy(add(dst, 0x28), add(src, 0x28), 0x10) // Copy 16 bytes from the middle of src to dst + mcopy(add(dst, 0x38), add(src, 0x00), 0x08) // Copy 8 zero bytes to dst + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// f(bytes): 0x20, 0x20, 0xffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff -> 0x20, 0x20, 0x0000000000000000776655443322110000112233445566770000000000000000 diff --git a/test/libsolidity/semanticTests/inlineAssembly/mcopy_as_identifier_pre_cancun.sol b/test/libsolidity/semanticTests/inlineAssembly/mcopy_as_identifier_pre_cancun.sol new file mode 100644 index 000000000000..4f50d1ddc263 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/mcopy_as_identifier_pre_cancun.sol @@ -0,0 +1,22 @@ +contract C { + function f() public pure returns (uint result) { + assembly { + let mcopy := 1 + result := mcopy + } + } + + function g() public pure returns (uint result) { + assembly { + function mcopy() -> r { + r := 1000 + } + result := mcopy() + } + } +} +// ==== +// EVMVersion: 1 +// g() -> 1000 diff --git a/test/libsolidity/semanticTests/inlineAssembly/mcopy_empty.sol b/test/libsolidity/semanticTests/inlineAssembly/mcopy_empty.sol new file mode 100644 index 000000000000..e34e443b3459 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/mcopy_empty.sol @@ -0,0 +1,18 @@ +contract C { + function mcopy_zero(bytes memory pattern) public pure returns (bytes memory out) { + out = pattern; + + // This should have no effect on output + assembly { + mcopy(add(out, 0x20), add(out, 0x30), 0) + mcopy(add(out, 0x30), add(out, 0x30), 0) + mcopy(add(out, 0x40), add(out, 0x30), 0) + + mcopy(add(out, 0x21), 0, 0) + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// mcopy_zero(bytes): 0x20, 0x20, 0xffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff -> 0x20, 0x20, 0xffeeddccbbaa9988776655443322110000112233445566778899aabbccddeeff diff --git a/test/libsolidity/semanticTests/inlineAssembly/mcopy_overlap.sol b/test/libsolidity/semanticTests/inlineAssembly/mcopy_overlap.sol new file mode 100644 index 000000000000..95f517daa9e1 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/mcopy_overlap.sol @@ -0,0 +1,39 @@ +function copy(uint dstOffset, uint srcOffset, uint length) pure returns (bytes memory out) { + out = + hex"2222222222222222333333333333333344444444444444445555555555555555" + hex"6666666666666666777777777777777788888888888888889999999999999999" + hex"aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd"; + assembly { + mcopy(add(add(out, 0x20), dstOffset), add(add(out, 0x20), srcOffset), length) + } +} + +contract C { + function mcopy_to_right_overlap() public pure returns (bytes memory) { + return copy(0x20, 0x10, 0x30); + } + + function mcopy_to_left_overlap() public pure returns (bytes memory) { + return copy(0x10, 0x20, 0x30); + } + + function mcopy_in_place() public pure returns (bytes memory) { + return copy(0x10, 0x10, 0x40); + } + + function mcopy_to_right_no_overlap() public pure returns (bytes memory) { + return copy(0x30, 0x10, 0x20); + } + + function mcopy_to_left_no_overlap() public pure returns (bytes memory) { + return copy(0x10, 0x30, 0x20); + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// mcopy_to_right_overlap() -> 0x20, 0x60, 0x2222222222222222333333333333333344444444444444445555555555555555, 0x4444444444444444555555555555555566666666666666667777777777777777, 0x88888888888888889999999999999999ccccccccccccccccdddddddddddddddd +// mcopy_to_left_overlap() -> 0x20, 0x60, 0x2222222222222222333333333333333366666666666666667777777777777777, 0x88888888888888889999999999999999aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb, 0xaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd +// mcopy_in_place() -> 0x20, 0x60, 0x2222222222222222333333333333333344444444444444445555555555555555, 0x6666666666666666777777777777777788888888888888889999999999999999, 0xaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd +// mcopy_to_right_no_overlap() -> 0x20, 0x60, 0x2222222222222222333333333333333344444444444444445555555555555555, 0x6666666666666666777777777777777744444444444444445555555555555555, 0x66666666666666667777777777777777ccccccccccccccccdddddddddddddddd +// mcopy_to_left_no_overlap() -> 0x20, 0x60, 0x2222222222222222333333333333333388888888888888889999999999999999, 0xaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb88888888888888889999999999999999, 0xaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd diff --git a/test/libsolidity/syntaxTests/inlineAssembly/mcopy.sol b/test/libsolidity/syntaxTests/inlineAssembly/mcopy.sol new file mode 100644 index 000000000000..c300160ed313 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/mcopy.sol @@ -0,0 +1,10 @@ +contract C { + function f() public pure { + assembly { + mcopy(0, 0, 0) + mcopy(0x1000, 0x2000, 100) + } + } +} +// ==== +// EVMVersion: >=cancun diff --git a/test/libsolidity/syntaxTests/inlineAssembly/mcopy_pre_cancun.sol b/test/libsolidity/syntaxTests/inlineAssembly/mcopy_pre_cancun.sol new file mode 100644 index 000000000000..090e2f62862c --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/mcopy_pre_cancun.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + assembly { + mcopy() + } + } +} +// ==== +// EVMVersion: r { + r := 1000 + } + result := mcopy() + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// ParserError 5568: (101-106): Cannot use builtin function name "mcopy" as identifier name. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure_cancun.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure_cancun.sol new file mode 100644 index 000000000000..23a7f4c45949 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure_cancun.sol @@ -0,0 +1,9 @@ +contract C { + function f() public pure { + assembly { + mcopy(1, 2, 3) + } + } +} +// ==== +// EVMVersion: >=cancun diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view_cancun.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view_cancun.sol index f68c313286de..8aacecdec7ed 100644 --- a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view_cancun.sol +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view_cancun.sol @@ -3,6 +3,7 @@ contract C { assembly { pop(blobhash(0)) pop(blobbasefee()) + mcopy(1, 2, 3) } } } diff --git a/test/libsolidity/syntaxTests/viewPureChecker/mcopy_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/mcopy_pure.sol new file mode 100644 index 000000000000..e89209dcbc84 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/mcopy_pure.sol @@ -0,0 +1,9 @@ +contract C { + function f() public pure { + assembly { + mcopy(0x1000, 0x2000, 100) + } + } +} +// ==== +// EVMVersion: >=cancun diff --git a/test/libyul/yulInterpreterTests/mcopy.yul b/test/libyul/yulInterpreterTests/mcopy.yul new file mode 100644 index 000000000000..7331544b8b1b --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy.yul @@ -0,0 +1,34 @@ +{ + mstore(0x00, 0x1111111111111111111111111111111122222222222222222222222222222222) + mstore(0x20, 0x3333333333333333333333333333333344444444444444444444444444444444) + mstore(0x40, 0x5555555555555555555555555555555566666666666666666666666666666666) + + mcopy(0, 0, 32) // No-op + mcopy(0x60, 0, 32) // Append a duplicate of the first word past msize + mcopy(0x90, 0x30, 1) // Copy the 0x44 byte from the middle of second word past msize + mcopy(0, 0, 0) // No-op + mcopy(0x2f, 0x90, 2) // Copy the 0x4400 straddling msize back into the the middle of second word + mcopy(0xa0, 0, 160) // Duplicate the whole thing +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(0, 0, 32) +// MCOPY(96, 0, 32) +// MCOPY(144, 48, 1) +// MCOPY(0, 0, 0) +// MCOPY(47, 144, 2) +// MCOPY(160, 0, 160) +// Memory dump: +// 0: 1111111111111111111111111111111122222222222222222222222222222222 +// 20: 3333333333333333333333333333334400444444444444444444444444444444 +// 40: 5555555555555555555555555555555566666666666666666666666666666666 +// 60: 1111111111111111111111111111111122222222222222222222222222222222 +// 80: 0000000000000000000000000000000044000000000000000000000000000000 +// A0: 1111111111111111111111111111111122222222222222222222222222222222 +// C0: 3333333333333333333333333333334400444444444444444444444444444444 +// E0: 5555555555555555555555555555555566666666666666666666666666666666 +// 100: 1111111111111111111111111111111122222222222222222222222222222222 +// 120: 0000000000000000000000000000000044000000000000000000000000000000 +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/mcopy_memory_access_out_of_range.yul b/test/libyul/yulInterpreterTests/mcopy_memory_access_out_of_range.yul new file mode 100644 index 000000000000..45599109b5a3 --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy_memory_access_out_of_range.yul @@ -0,0 +1,21 @@ +{ + mstore8(0xffffffffffffffff, 1) + + // Interpreter ignores memory copies with very large offset and/or size. + // None of these will show up in memory dump. + mcopy(0, 0xffffffffffffffff, 1) + mcopy(0xffffffffffffffff, 0, 1) + mcopy(0, 1, 0xffffffffffffffff) + mcopy(0xffffffff00000000, 0xffffffffffffffff, 0xffffffffffffffff) +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(0, 0xffffffffffffffff, 1) +// MCOPY(0xffffffffffffffff, 0, 1) +// MCOPY(0, 1, 0xffffffffffffffff) +// MCOPY(0xffffffff00000000, 0xffffffffffffffff, 0xffffffffffffffff) +// Memory dump: +// FFFFFFFFFFFFFFE0: 0000000000000000000000000000000000000000000000000000000000000001 +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_read.yul b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_read.yul new file mode 100644 index 000000000000..b0b2b561e747 --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_read.yul @@ -0,0 +1,12 @@ +{ + // Should not affect msize + mcopy(0x30, 0x30, 0) + sstore(0, msize()) +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(48, 48, 0) +// Memory dump: +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_write.yul b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_write.yul new file mode 100644 index 000000000000..0448b20c0669 --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_on_write.yul @@ -0,0 +1,13 @@ +{ + // Should expand memory to two full words (0x40 bytes) + mcopy(0x30, 0, 1) + sstore(0, msize()) +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(48, 0, 1) +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000040 diff --git a/test/libyul/yulInterpreterTests/mcopy_memory_expansion_zero_size.yul b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_zero_size.yul new file mode 100644 index 000000000000..0448b20c0669 --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy_memory_expansion_zero_size.yul @@ -0,0 +1,13 @@ +{ + // Should expand memory to two full words (0x40 bytes) + mcopy(0x30, 0, 1) + sstore(0, msize()) +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(48, 0, 1) +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000040 diff --git a/test/libyul/yulInterpreterTests/mcopy_overlap.yul b/test/libyul/yulInterpreterTests/mcopy_overlap.yul new file mode 100644 index 000000000000..2ac2460d7fe3 --- /dev/null +++ b/test/libyul/yulInterpreterTests/mcopy_overlap.yul @@ -0,0 +1,21 @@ +{ + mstore(0x00, 0x0000000000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x1111111122222222333333334444444455555555666666667777777788888888) + mstore(0x40, 0x1111111122222222333333334444444455555555666666667777777788888888) + mstore(0x60, 0x0000000000000000000000000000000000000000000000000000000000000000) + + mcopy(0x08, 0x20, 0x20) + mcopy(0x58, 0x40, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// Trace: +// MCOPY(8, 32, 32) +// MCOPY(88, 64, 32) +// Memory dump: +// 0: 0000000000000000111111112222222233333333444444445555555566666666 +// 20: 7777777788888888333333334444444455555555666666667777777788888888 +// 40: 1111111122222222333333334444444455555555666666661111111122222222 +// 60: 3333333344444444555555556666666677777777888888880000000000000000 +// Storage dump: diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy.yul new file mode 100644 index 000000000000..aaa36047a448 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy.yul @@ -0,0 +1,19 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + + mcopy(_0, _32, _32) // Not redundant. Read/write between different areas. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// mcopy(_0, _32, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore.yul new file mode 100644 index 000000000000..cb6364b83b9d --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore.yul @@ -0,0 +1,23 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mstore(_0, mem32) + mcopy(_0, _32, _32) // Redundant with MSTORE. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// mcopy(_0, _32, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore_overlap.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore_overlap.yul new file mode 100644 index 000000000000..4dbe80fe7dfe --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_mstore_overlap.yul @@ -0,0 +1,29 @@ +{ + let _0 := 0 + let _1 := 1 + let _31 := 31 + let _32 := 32 + let _33 := 33 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mstore(_0, mem32) + mcopy(_1, _33, _31) // Not redundant. MCOPY does not copy all of it. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _1 := 1 +// let _31 := 31 +// let _32 := 32 +// let _33 := 33 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// mcopy(_1, _33, _31) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area.yul new file mode 100644 index 000000000000..d7e672f190c5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area.yul @@ -0,0 +1,19 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x20) + + mcopy(_0, _0, _32) // Redundant (no-op) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x20) +// mcopy(_0, _0, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area_mcopy.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area_mcopy.yul new file mode 100644 index 000000000000..2b11528fba72 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_area_mcopy.yul @@ -0,0 +1,21 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + + mcopy(_0, _32, _32) + mcopy(_0, _32, _32) // Redundant with previous MCOPY. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// mcopy(_0, _32, _32) +// mcopy(_0, _32, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mcopy.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mcopy.yul new file mode 100644 index 000000000000..12cf27c8666b --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mcopy.yul @@ -0,0 +1,23 @@ +{ + let _0 := 0 + let _32 := 32 + let _64 := 64 + + calldatacopy(0, 0, 0x60) + + mcopy(_0, _64, _32) + mcopy(_32, _64, _32) // Not redundant. Writing to a different area. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// let _64 := 64 +// calldatacopy(0, 0, 0x60) +// mcopy(_0, _64, _32) +// mcopy(_32, _64, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore.yul new file mode 100644 index 000000000000..9802bfb517b0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore.yul @@ -0,0 +1,23 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mstore(_0, mem32) + mcopy(_0, _0, _32) // Redundant (no-op) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// mcopy(_0, _0, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore8_mstore8.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore8_mstore8.yul new file mode 100644 index 000000000000..8db2b800ce7e --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore8_mstore8.yul @@ -0,0 +1,27 @@ +{ + let _0 := 0 + let _1 := 1 + let _31 := 31 + + calldatacopy(0, 0, 0x40) + let mem31 := mload(_31) + + mstore8(_0, mem31) // Redundant. Overwritten by MSTORE. + mcopy(_0, _0, _1) // Redundant (no-op) + mstore8(_0, mem31) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _1 := 1 +// let _31 := 31 +// calldatacopy(0, 0, 0x40) +// let mem31 := mload(_31) +// mstore8(_0, mem31) +// mcopy(_0, _0, _1) +// mstore8(_0, mem31) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore.yul new file mode 100644 index 000000000000..cc080803f15c --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore.yul @@ -0,0 +1,25 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mstore(_0, mem32) + mcopy(_0, _0, _32) // Redundant (no-op) + mstore(_0, mem32) // Redundant with previous MSTORE. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// mcopy(_0, _0, _32) +// mstore(_0, mem32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore_overlap.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore_overlap.yul new file mode 100644 index 000000000000..639d026604f6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_read_area_mstore_mstore_overlap.yul @@ -0,0 +1,27 @@ +{ + let _0 := 0 + let _1 := 1 + let _32 := 32 + + calldatacopy(0, 0, 0x20) + let mem32 := mload(_32) + + mstore(_0, mem32) + mcopy(_1, _1, _32) // Redundant (no-op) + mstore(_0, mem32) // Redundant with previous MSTORE. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _1 := 1 +// let _32 := 32 +// calldatacopy(0, 0, 0x20) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// mcopy(_1, _1, _32) +// mstore(_0, mem32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_write_area_mcopy.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_write_area_mcopy.yul new file mode 100644 index 000000000000..e083572b4772 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mcopy_same_write_area_mcopy.yul @@ -0,0 +1,23 @@ +{ + let _0 := 0 + let _32 := 32 + let _64 := 64 + + calldatacopy(0, 0, 0x60) + + mcopy(_0, _32, _32) + mcopy(_0, _64, _32) // Not redundant. Copying from a different spot. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// let _64 := 64 +// calldatacopy(0, 0, 0x60) +// mcopy(_0, _32, _32) +// mcopy(_0, _64, _32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore8_mstore8.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore8_mstore8.yul new file mode 100644 index 000000000000..e907a3017048 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore8_mstore8.yul @@ -0,0 +1,21 @@ +{ + let _0 := 0 + let _31 := 31 + + calldatacopy(0, 0, 0x40) + let mem31 := mload(_31) + + mstore8(_0, mem31) + mstore8(_0, mem31) // Redundant with previous MSTORE8. +} +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _31 := 31 +// calldatacopy(0, 0, 0x40) +// let mem31 := mload(_31) +// mstore8(_0, mem31) +// mstore8(_0, mem31) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mcopy.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mcopy.yul new file mode 100644 index 000000000000..fd35befcc823 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mcopy.yul @@ -0,0 +1,23 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mcopy(_0, _32, _32) + mstore(_0, mem32) // Redundant with MCOPY. +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mcopy(_0, _32, _32) +// mstore(_0, mem32) +// } diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mstore.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mstore.yul new file mode 100644 index 000000000000..b133a68f6687 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_mstore.yul @@ -0,0 +1,20 @@ +{ + let _0 := 0 + let _32 := 32 + + calldatacopy(0, 0, 0x40) + let mem32 := mload(_32) + + mstore(_0, mem32) // Redundant. Overwritten by MSTORE. + mstore(_0, mem32) +} +// ---- +// step: equalStoreEliminator +// +// { +// let _0 := 0 +// let _32 := 32 +// calldatacopy(0, 0, 0x40) +// let mem32 := mload(_32) +// mstore(_0, mem32) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_non_zero_size.sol b/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_non_zero_size.sol new file mode 100644 index 000000000000..410b7df536b3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_non_zero_size.sol @@ -0,0 +1,22 @@ +{ + calldatacopy(0, 0, 0x60) + + mcopy(0x20, 0x40, 0x20) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: expressionSimplifier +// +// { +// { +// let _1 := 0x60 +// let _2 := 0 +// calldatacopy(_2, _2, _1) +// let _4 := 0x20 +// mcopy(_4, 0x40, _4) +// return(_2, _1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_zero_size.sol b/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_zero_size.sol new file mode 100644 index 000000000000..fe4420110561 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/mcopy_zero_size.sol @@ -0,0 +1,21 @@ +{ + calldatacopy(0, 0, 0x60) + + mcopy(0x20, 0x40, 0) // Equivalent to mcopy(0, 0, 0) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: expressionSimplifier +// +// { +// { +// let _1 := 0x60 +// let _2 := 0 +// calldatacopy(_2, _2, _1) +// mcopy(0, 0, _2) +// return(_2, _1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/mcopy.sol b/test/libyul/yulOptimizerTests/fullSuite/mcopy.sol new file mode 100644 index 000000000000..b04852429f80 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/mcopy.sol @@ -0,0 +1,23 @@ +{ + calldatacopy(0, 0, 0x40) + + mcopy(0x20, 0, 0x20) // Not redundant. MCOPY reads it. + mcopy(0x40, 0x10, 0x30) + mstore(0x20, 42) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: fullSuite +// +// { +// { +// calldatacopy(0, 0, 0x40) +// mcopy(0x20, 0, 0x20) +// mcopy(0x40, 0x10, 0x30) +// mstore(0x20, 42) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_overwritten.sol b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_overwritten.sol new file mode 100644 index 000000000000..252363de0ef7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_overwritten.sol @@ -0,0 +1,21 @@ +{ + calldatacopy(0, 0, 0x40) + + mcopy(0, 0x20, 0x20) // Redundant. Overwritten by MSTORE. + mstore(0, 42) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: fullSuite +// +// { +// { +// calldatacopy(0, 0, 0x40) +// mcopy(0, 0x20, 0x20) +// mstore(0, 42) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_same_area.sol b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_same_area.sol new file mode 100644 index 000000000000..a39f5b689b39 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_same_area.sol @@ -0,0 +1,19 @@ +{ + calldatacopy(0, 0, 0x20) + + mcopy(0, 0, 0x20) // Redundant. Does not change any values. + + return(0, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: fullSuite +// +// { +// { +// calldatacopy(0, 0, 0x20) +// mcopy(0, 0, 0x20) +// return(0, 0x20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_zero_size.sol b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_zero_size.sol new file mode 100644 index 000000000000..3ae7c2c31330 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/mcopy_redundant_zero_size.sol @@ -0,0 +1,19 @@ +{ + calldatacopy(0, 0, 0x40) + + mcopy(0, 0x20, 0) // Redundant. Does not copy anything. + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: fullSuite +// +// { +// { +// calldatacopy(0, 0, 0x40) +// mcopy(0, 0, 0) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mcopy_no_overlap.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mcopy_no_overlap.yul new file mode 100644 index 000000000000..51b6da83fa02 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mcopy_no_overlap.yul @@ -0,0 +1,31 @@ +{ + calldatacopy(0, 0, 0x80) + + let _0 := 0 + let _32 := 32 + let _64 := 64 + let _96 := 96 + + // Sanity check: independent MCOPY is not affected + mcopy(_0, _32, _32) // Not redundant. Not being overwritten. + mcopy(_64, _96, _32) + + return(0, 0x80) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x80) +// let _0 := 0 +// let _32 := 32 +// let _64 := 64 +// let _96 := 96 +// mcopy(_0, _32, _32) +// mcopy(_64, _96, _32) +// return(0, 0x80) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mstore_no_overlap.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mstore_no_overlap.yul new file mode 100644 index 000000000000..0ff179d4a4d2 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_mstore_no_overlap.yul @@ -0,0 +1,31 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _64 := 64 + + // Sanity check: independent MCOPY and MSTORE are not affected + mcopy(_0, _32, _32) // Not redundant. Not being overwritten. + mstore(_64, _42) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _64 := 64 +// mcopy(_0, _32, _32) +// mstore(_64, _42) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_non_overlapping_areas.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_non_overlapping_areas.yul new file mode 100644 index 000000000000..48e1a4ec1ecb --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_non_overlapping_areas.yul @@ -0,0 +1,24 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + + mcopy(_0, _32, _32) // Not redundant. Not being overwritten. + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// mcopy(_0, _32, _32) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overlapping_areas.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overlapping_areas.yul new file mode 100644 index 000000000000..70d67640a9c9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overlapping_areas.yul @@ -0,0 +1,25 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _32 := 32 + let _64 := 64 + + mcopy(_0, _32, _64) // Not redundant. Not being overwritten. + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _32 := 32 +// mcopy(_0, _32, 64) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mcopy.yul new file mode 100644 index 000000000000..3334580fcfe2 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mcopy.yul @@ -0,0 +1,26 @@ +{ + calldatacopy(0, 0, 0x80) + + let _0 := 0 + let _64 := 64 + + mcopy(_0, _64, _64) // Redundant. MCOPY overwrites it. + mcopy(_0, _64, _64) + + return(0, 0x80) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x80) +// let _0 := 0 +// let _64 := 64 +// mcopy(_0, _64, _64) +// mcopy(_0, _64, _64) +// return(0, 0x80) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore.yul new file mode 100644 index 000000000000..0c4490a44f94 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore.yul @@ -0,0 +1,27 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + + mstore(_0, _42) // Redundant. MCOPY overwrites it. + mcopy(_0, _32, _32) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// mcopy(_0, _32, _32) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore_overlap.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore_overlap.yul new file mode 100644 index 000000000000..1c99063f5c94 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_overwriting_mstore_overlap.yul @@ -0,0 +1,32 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _1 := 1 + let _32 := 32 + let _42 := 42 + let _64 := 64 + + mstore(_0, _42) // Not redundant. MCOPY does not overwrite all of it. + mcopy(_1, _64, _32) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _1 := 1 +// let _32 := 32 +// let _42 := 42 +// let _64 := 64 +// mstore(_0, _42) +// mcopy(_1, _64, _32) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mcopy.yul new file mode 100644 index 000000000000..96d69ffbba0f --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mcopy.yul @@ -0,0 +1,31 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _64 := 64 + + mcopy(_32, _64, _32) // Not redundant. MCOPY reads it. + mcopy(_0, _32, _32) + mstore(_32, _42) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// mcopy(_32, 64, _32) +// mcopy(_0, _32, _32) +// mstore(_32, _42) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore.yul new file mode 100644 index 000000000000..d0c50bfbf1c1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore.yul @@ -0,0 +1,32 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + mstore(_0, _42) // Not redundant. MCOPY reads it. + mcopy(_32, _0, _32) + mstore(_0, _123) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mstore(_0, _42) +// mcopy(_32, _0, _32) +// mstore(_0, _123) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore8.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore8.yul new file mode 100644 index 000000000000..c6dcbc188c8a --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore8.yul @@ -0,0 +1,34 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _1 := 1 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + mstore8(_0, _42) // Not redundant. MCOPY reads it. + mcopy(_32, _0, _1) + mstore8(_0, _123) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _1 := 1 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mstore8(_0, _42) +// mcopy(_32, _0, _1) +// mstore8(_0, _123) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overhang.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overhang.yul new file mode 100644 index 000000000000..98a77592ea1c --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overhang.yul @@ -0,0 +1,34 @@ +{ + calldatacopy(0, 0, 0x120) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _96 := 96 + let _123 := 123 + + mstore(_32, _42) // Not redundant. MCOPY reads it. + mcopy(_96, _0, _96) + mstore(_32, _123) + + return(0, 0x120) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x120) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _96 := 96 +// let _123 := 123 +// mstore(_32, _42) +// mcopy(_96, _0, _96) +// mstore(_32, _123) +// return(0, 0x120) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overlap.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overlap.yul new file mode 100644 index 000000000000..32a1fbf4e1c1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_overlap.yul @@ -0,0 +1,36 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _31 := 31 + let _32 := 32 + let _42 := 42 + let _64 := 64 + let _123 := 123 + + mstore(_0, _42) // Not redundant. MCOPY reads part of it. + mcopy(_64, _31, _32) + mstore(_0, _123) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _31 := 31 +// let _32 := 32 +// let _42 := 42 +// let _64 := 64 +// let _123 := 123 +// mstore(_0, _42) +// mcopy(_64, _31, _32) +// mstore(_0, _123) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_underhang.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_underhang.yul new file mode 100644 index 000000000000..b339d2843460 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_mstore_underhang.yul @@ -0,0 +1,36 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _1 := 1 + let _16 := 16 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + mstore(_0, _42) // Not redundant. MCOPY reads part of it. + mcopy(_32, _16, _1) + mstore(_0, _123) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _1 := 1 +// let _16 := 16 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mstore(_0, _42) +// mcopy(_32, _16, _1) +// mstore(_0, _123) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_overlapping_areas_mstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_overlapping_areas_mstore.yul new file mode 100644 index 000000000000..fa73f0171a33 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_overlapping_areas_mstore.yul @@ -0,0 +1,34 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _64 := 64 + let _123 := 123 + + mstore(_32, _42) // Redundant. MCOPY overwrites it. + mcopy(_0, _32, _64) + mstore(_32, _123) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _64 := 64 +// let _123 := 123 +// mstore(_32, _42) +// mcopy(_0, _32, _64) +// mstore(_32, _123) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mcopy.yul new file mode 100644 index 000000000000..b7227913c15f --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mcopy.yul @@ -0,0 +1,30 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + + mcopy(_0, _32, _32) // Redundant. MSTORE overwrites it. + mcopy(_0, _0, _32) // Redundant (no-op) + mstore(_32, _42) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// mcopy(_0, _32, _32) +// mcopy(_0, _0, _32) +// mstore(_32, _42) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore.yul new file mode 100644 index 000000000000..5edbc22b5f88 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore.yul @@ -0,0 +1,34 @@ +{ + calldatacopy(0, 0, 0x20) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + mstore(_0, _42) // Redundant. MSTORE overwrites it. + mcopy(_0, _0, _32) // Redundant (no-op) + mstore(_0, _123) + + return(0, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// let _1 := 0x20 +// let _2 := 0 +// let _3 := 0 +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mstore(_0, _42) +// mcopy(_0, _0, _32) +// mstore(_0, _123) +// return(0, 0x20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore8.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore8.yul new file mode 100644 index 000000000000..aab661f62bb9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore8.yul @@ -0,0 +1,32 @@ +{ + calldatacopy(0, 0, 0x20) + + let _0 := 0 + let _1 := 1 + let _42 := 42 + let _123 := 123 + + mstore8(_0, _42) // Redundant. MSTORE8 overwrites it. + mcopy(_0, _0, _1) // Redundant (no-op) + mstore8(_0, _123) + + return(0, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x20) +// let _0 := 0 +// let _1 := 1 +// let _42 := 42 +// let _123 := 123 +// mstore8(_0, _42) +// mcopy(_0, _0, _1) +// mstore8(_0, _123) +// return(0, 0x20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore_overlap.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore_overlap.yul new file mode 100644 index 000000000000..36b4fc489ff9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_reading_writing_same_area_mstore_overlap.yul @@ -0,0 +1,34 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _1 := 1 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + mstore(_0, _42) // Redundant. MSTORE overwrites it. + mcopy(_1, _1, _32) // Redundant (no-op) + mstore(_0, _123) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _1 := 1 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mstore(_0, _42) +// mcopy(_1, _1, _32) +// mstore(_0, _123) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_area.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_area.yul new file mode 100644 index 000000000000..1d0f86b86aa7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_area.yul @@ -0,0 +1,23 @@ +{ + calldatacopy(0, 0, 0x20) + + let _0 := 0 + let _32 := 32 + + mcopy(_0, _0, _32) // Redundant (no-op) + + return(0, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x20) +// let _0 := 0 +// mcopy(_0, _0, 32) +// return(0, 0x20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_write_area_overwriting_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_write_area_overwriting_mcopy.yul new file mode 100644 index 000000000000..0e473a3599a7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_same_write_area_overwriting_mcopy.yul @@ -0,0 +1,28 @@ +{ + calldatacopy(0, 0, 0x60) + + let _0 := 0 + let _32 := 32 + let _64 := 64 + + mcopy(_0, _32, _32) // Redundant. MCOPY overwrites it. + mcopy(_0, _64, _32) + + return(0, 0x60) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x60) +// let _0 := 0 +// let _32 := 32 +// let _64 := 64 +// mcopy(_0, _32, _32) +// mcopy(_0, _64, _32) +// return(0, 0x60) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_sstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_sstore.yul new file mode 100644 index 000000000000..87fd89d3a750 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mcopy_sstore.yul @@ -0,0 +1,32 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + let _123 := 123 + + // Sanity check: MCOPY does not affect storage + sstore(_0, _42) // Redundant. SSTORE overwrites it. + mcopy(_32, _0, _32) + sstore(_0, _123) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// let _123 := 123 +// mcopy(_32, _0, _32) +// sstore(_0, _123) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mload_reading_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mload_reading_mcopy.yul new file mode 100644 index 000000000000..759e631f60bc --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mload_reading_mcopy.yul @@ -0,0 +1,32 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + + mcopy(_0, _32, _32) // Not redundant. MLOAD reads it. + let mem0 := mload(_0) + mstore(_0, _42) + + sstore(_0, mem0) + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// mcopy(_0, _32, _32) +// let mem0 := mload(_0) +// mstore(_0, _42) +// sstore(_0, mem0) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mcopy.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mcopy.yul new file mode 100644 index 000000000000..f32267a30c55 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mcopy.yul @@ -0,0 +1,28 @@ +{ + calldatacopy(0, 0, 0x40) + + let _0 := 0 + let _32 := 32 + let _42 := 42 + + mcopy(_0, _32, _32) // Redundant. MSTORE overwrites it. + mstore(_0, _42) + + return(0, 0x40) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// calldatacopy(0, 0, 0x40) +// let _0 := 0 +// let _32 := 32 +// let _42 := 42 +// mcopy(_0, _32, _32) +// mstore(_0, _42) +// return(0, 0x40) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mstore.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mstore.yul new file mode 100644 index 000000000000..83208b8eede7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/mstore_overwriting_mstore.yul @@ -0,0 +1,28 @@ +{ + calldatacopy(0, 0, 0x20) + + let _0 := 0 + let _42 := 42 + let _123 := 123 + + mstore(_0, _42) // Redundant. MSTORE overwrites it. + mstore(_0, _123) + + return(0, 0x20) +} +// ==== +// EVMVersion: >=cancun +// ---- +// step: unusedStoreEliminator +// +// { +// { +// let _1 := 0x20 +// let _2 := 0 +// let _3 := 0 +// let _0 := 0 +// let _42 := 42 +// mstore(_0, 123) +// return(0, 0x20) +// } +// } diff --git a/test/libyul/yulSyntaxTests/mcopy.yul b/test/libyul/yulSyntaxTests/mcopy.yul new file mode 100644 index 000000000000..1f5d042fed6e --- /dev/null +++ b/test/libyul/yulSyntaxTests/mcopy.yul @@ -0,0 +1,5 @@ +{ + mcopy(0x100, 0x200, 0x300) +} +// ==== +// EVMVersion: >=cancun diff --git a/test/libyul/yulSyntaxTests/mcopy_as_identifier.yul b/test/libyul/yulSyntaxTests/mcopy_as_identifier.yul new file mode 100644 index 000000000000..e48c27d06d81 --- /dev/null +++ b/test/libyul/yulSyntaxTests/mcopy_as_identifier.yul @@ -0,0 +1,14 @@ +{ + { + let mcopy := 1 + } + + { + function mcopy() {} + mcopy() + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// ParserError 5568: (20-25): Cannot use builtin function name "mcopy" as identifier name. diff --git a/test/libyul/yulSyntaxTests/mcopy_as_identifier_pre_cancun.yul b/test/libyul/yulSyntaxTests/mcopy_as_identifier_pre_cancun.yul new file mode 100644 index 000000000000..096fce4ed3bb --- /dev/null +++ b/test/libyul/yulSyntaxTests/mcopy_as_identifier_pre_cancun.yul @@ -0,0 +1,12 @@ +{ + { + let mcopy := 1 + } + + { + function mcopy() {} + mcopy() + } +} +// ==== +// EVMVersion: =cancun +// ---- +// TypeError 7000: (6-11): Function "mcopy" expects 3 arguments but got 4. +// TypeError 7000: (44-49): Function "mcopy" expects 3 arguments but got 2. +// TypeError 7000: (68-73): Function "mcopy" expects 3 arguments but got 1. +// TypeError 7000: (85-90): Function "mcopy" expects 3 arguments but got 0. diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 0466d6f66984..12a046d70e74 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -87,6 +87,22 @@ void copyZeroExtended( _target[_targetOffset + i] = (_sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0); } +void copyZeroExtendedWithOverlap( + std::map& _target, + std::map const& _source, + size_t _targetOffset, + size_t _sourceOffset, + size_t _size +) +{ + if (_targetOffset >= _sourceOffset) + for (size_t i = _size; i > 0; --i) + _target[_targetOffset + i - 1] = (_source.count(_sourceOffset + i - 1) != 0 ? _source.at(_sourceOffset + i - 1) : 0); + else + for (size_t i = 0; i < _size; ++i) + _target[_targetOffset + i] = (_source.count(_sourceOffset + i) != 0 ? _source.at(_sourceOffset + i) : 0); +} + } using u512 = boost::multiprecision::number>; @@ -262,6 +278,17 @@ u256 EVMInstructionInterpreter::eval( ); logTrace(_instruction, arg); return 0; + case Instruction::MCOPY: + if (accessMemory(arg[1], arg[2]) && accessMemory(arg[0], arg[2])) + copyZeroExtendedWithOverlap( + m_state.memory, + m_state.memory, + static_cast(arg[0]), + static_cast(arg[1]), + static_cast(arg[2]) + ); + logTrace(_instruction, arg); + return 0; case Instruction::BLOCKHASH: if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber) return 0; diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index 2f50217ed107..d3d221112991 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -56,6 +56,19 @@ void copyZeroExtended( size_t _size ); +/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to +/// @a _target at offset @a _targetOffset. Behaves as if @a _source would +/// continue with an infinite sequence of zero bytes beyond its end. +/// When target and source areas overlap, behaves as if the data was copied +/// using an intermediate buffer. +void copyZeroExtendedWithOverlap( + std::map& _target, + std::map const& _source, + size_t _targetOffset, + size_t _sourceOffset, + size_t _size +); + struct InterpreterState; /**