Skip to content

Commit

Permalink
Merge pull request #14779 from ethereum/mcopy
Browse files Browse the repository at this point in the history
Yul builtin for `MCOPY`
  • Loading branch information
cameel authored Jan 24, 2024
2 parents 89a70c7 + bd76278 commit 8f5ee88
Show file tree
Hide file tree
Showing 90 changed files with 1,806 additions and 11 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand Down
2 changes: 1 addition & 1 deletion docs/grammar/SolidityLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions docs/using-the-compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ at each version. Backward compatibility is not guaranteed between each version.
- ``cancun``
- The block's blob base fee (`EIP-7516 <https://eips.ethereum.org/EIPS/eip-7516>`_ and `EIP-4844 <https://eips.ethereum.org/EIPS/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 <https://eips.ethereum.org/EIPS/eip-4844>`_).
- Opcode ``mcopy`` is available in assembly (see `EIP-5656 <https://eips.ethereum.org/EIPS/eip-5656>`_).

.. index:: ! standard JSON, ! --standard-json
.. _compiler-api:
Expand Down
2 changes: 2 additions & 0 deletions docs/yul.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
10 changes: 10 additions & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ std::map<std::string, Instruction> 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 },
Expand Down Expand Up @@ -222,6 +223,7 @@ static std::map<Instruction, InstructionInfo> 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 } },
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ std::vector<SemanticInformation::Operation> 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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions libevmasm/SimplificationRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct EVMBuiltins
static auto constexpr EXTCODECOPY = PatternGenerator<Instruction::EXTCODECOPY>{};
static auto constexpr RETURNDATASIZE = PatternGenerator<Instruction::RETURNDATASIZE>{};
static auto constexpr RETURNDATACOPY = PatternGenerator<Instruction::RETURNDATACOPY>{};
static auto constexpr MCOPY = PatternGenerator<Instruction::MCOPY>{};
static auto constexpr EXTCODEHASH = PatternGenerator<Instruction::EXTCODEHASH>{};
static auto constexpr BLOCKHASH = PatternGenerator<Instruction::BLOCKHASH>{};
static auto constexpr BLOBHASH = PatternGenerator<Instruction::BLOBHASH>{};
Expand Down
2 changes: 2 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions liblangutil/EVMVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 9 additions & 1 deletion libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ std::set<YulString> 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
Expand All @@ -150,7 +157,8 @@ std::set<YulString> createReservedIdentifiers(langutil::EVMVersion _evmVersion)
!baseFeeException(instr.second) &&
!prevRandaoException(name) &&
!blobHashException(instr.second) &&
!blobBaseFeeException(instr.second)
!blobBaseFeeException(instr.second) &&
!mcopyException(instr.second)
)
reserved.emplace(name);
}
Expand Down
3 changes: 3 additions & 0 deletions libyul/optimiser/UnusedStoreEliminator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions scripts/test_antlr_grammar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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' |
Expand Down
108 changes: 108 additions & 0 deletions test/libsolidity/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -339,6 +340,113 @@ BOOST_AUTO_TEST_CASE(complex_control_flow)
testRunTimeGas("ln(int128)", std::vector<bytes>{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()

}
12 changes: 12 additions & 0 deletions test/libsolidity/memoryGuardTests/marked_empty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
contract C {
constructor() {
assembly ("memory-safe") {}
}

function f() public {
assembly ("memory-safe") {}
}
}
// ----
// :C(creation) true
// :C(runtime) true
16 changes: 16 additions & 0 deletions test/libsolidity/memoryGuardTests/marked_with_memory_access.sol
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions test/libsolidity/memoryGuardTests/unmarked_empty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
contract C {
constructor() {
assembly {}
}

function f() public {
assembly {}
}
}
// ----
// :C(creation) true
// :C(runtime) true

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 8f5ee88

Please sign in to comment.