diff --git a/Changelog.md b/Changelog.md index 18e0deeaf612..433cdf31be10 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.8.31 (unreleased) Language Features: +* Yul: Introduce builtin ``clz(x)`` for counting the number of leading zero bits in a 256-bit word. Compiler Features: * ethdebug: Experimental support for instructions and source locations under EOF. diff --git a/docs/grammar/SolidityLexer.g4 b/docs/grammar/SolidityLexer.g4 index ad3b04098132..510b1f180a44 100644 --- a/docs/grammar/SolidityLexer.g4 +++ b/docs/grammar/SolidityLexer.g4 @@ -300,7 +300,7 @@ YulHex: 'hex'; YulEVMBuiltin: 'stop' | 'add' | 'sub' | 'mul' | 'div' | 'sdiv' | 'mod' | 'smod' | 'exp' | 'not' | 'lt' | 'gt' | 'slt' | 'sgt' | 'eq' | 'iszero' | 'and' | 'or' | 'xor' | 'byte' - | 'shl' | 'shr' | 'sar' | 'addmod' | 'mulmod' | 'signextend' | 'keccak256' + | 'shl' | 'shr' | 'sar' | 'clz' | 'addmod' | 'mulmod' | 'signextend' | 'keccak256' | 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'tload' | 'tstore'| 'msize' | 'gas' | 'address' | 'balance' | 'selfbalance' | 'caller' | 'callvalue' | 'calldataload' | 'calldatasize' | 'calldatacopy' | 'extcodesize' | 'extcodecopy' | 'returndatasize' diff --git a/docs/yul.rst b/docs/yul.rst index c81c9562a58c..5c8086b00120 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`` and ``N`` are present since Frontier, -Homestead, Byzantium, Constantinople, Istanbul, London, Paris or Cancun respectively. +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. 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 @@ -812,6 +812,8 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a +-------------------------+-----+---+-----------------------------------------------------------------+ | sar(x, y) | | C | signed arithmetic shift right y by x bits | +-------------------------+-----+---+-----------------------------------------------------------------+ +| clz(x) | | O | number of leading zero bits of x, 256 if x == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ | addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic, 0 if m == 0 | +-------------------------+-----+---+-----------------------------------------------------------------+ | mulmod(x, y, m) | | F | (x * y) % m with arbitrary precision arithmetic, 0 if m == 0 | diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index b5568763573a..e6648d5620a7 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -51,6 +51,7 @@ std::map> const solidity::evmasm::c_instru { "SHL", Instruction::SHL }, { "SHR", Instruction::SHR }, { "SAR", Instruction::SAR }, + { "CLZ", Instruction::CLZ }, { "ADDMOD", Instruction::ADDMOD }, { "MULMOD", Instruction::MULMOD }, { "SIGNEXTEND", Instruction::SIGNEXTEND }, @@ -219,6 +220,7 @@ static std::map const c_instructionInfo = {Instruction::SHL, {"SHL", 0, 2, 1, false, Tier::VeryLow}}, {Instruction::SHR, {"SHR", 0, 2, 1, false, Tier::VeryLow}}, {Instruction::SAR, {"SAR", 0, 2, 1, false, Tier::VeryLow}}, + {Instruction::CLZ, {"CLZ", 0, 1, 1, false, Tier::Low}}, {Instruction::ADDMOD, {"ADDMOD", 0, 3, 1, false, Tier::Mid}}, {Instruction::MULMOD, {"MULMOD", 0, 3, 1, false, Tier::Mid}}, {Instruction::SIGNEXTEND, {"SIGNEXTEND", 0, 2, 1, false, Tier::Low}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index cfdb85d2fa4c..d5b30f463c40 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -61,6 +61,7 @@ enum class Instruction: uint8_t SHL, ///< bitwise SHL operation SHR, ///< bitwise SHR operation SAR, ///< bitwise SAR operation + CLZ, ///< count of leading zeros in binary representation KECCAK256 = 0x20, ///< compute KECCAK-256 hash diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index 6d34a50410fb..ea1baa9e530c 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -98,6 +98,7 @@ struct EVMBuiltins static auto constexpr SHL = PatternGenerator{}; static auto constexpr SHR = PatternGenerator{}; static auto constexpr SAR = PatternGenerator{}; + static auto constexpr CLZ = PatternGenerator{}; static auto constexpr ADDMOD = PatternGenerator{}; static auto constexpr MULMOD = PatternGenerator{}; static auto constexpr SIGNEXTEND = PatternGenerator{}; diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index 4fbfe8de41b0..33f49ff912fa 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -42,6 +42,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _eofVersi case Instruction::SHR: case Instruction::SAR: return hasBitwiseShifting(); + case Instruction::CLZ: + return hasCLZ(); case Instruction::CREATE2: return !_eofVersion.has_value() && hasCreate2(); case Instruction::EXTCODEHASH: diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index 38580621a45e..75e9ba454686 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -128,6 +128,7 @@ class EVMVersion: bool supportsReturndata() const { return *this >= byzantium(); } bool hasStaticCall() const { return *this >= byzantium(); } bool hasBitwiseShifting() const { return *this >= constantinople(); } + bool hasCLZ() const { return *this >= osaka(); } bool hasCreate2() const { return *this >= constantinople(); } bool hasExtCodeHash() const { return *this >= constantinople(); } bool hasChainID() const { return *this >= istanbul(); } diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index b36eb48b415e..79d8bfd26860 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -834,6 +834,8 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio errorForVM(7458_error, "only available for Constantinople-compatible"); else if (_instr == evmasm::Instruction::SAR && !m_evmVersion.hasBitwiseShifting()) errorForVM(2054_error, "only available for Constantinople-compatible"); + else if (_instr == evmasm::Instruction::CLZ && !m_evmVersion.hasCLZ()) + errorForVM(4948_error, "only available for Osaka-compatible"); else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) errorForVM(6166_error, "only available for Constantinople-compatible"); else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index d68ef16348b1..9bc86353a7a9 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -152,6 +152,12 @@ std::set> createReservedIdentifiers(langutil::EVMVersio _evmVersion < langutil::EVMVersion::cancun() && (_instr == evmasm::Instruction::TSTORE || _instr == evmasm::Instruction::TLOAD); }; + // TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name + // clz for VMs before osaka. + auto clzException = [&](evmasm::Instruction _instr) -> bool + { + return _instr == evmasm::Instruction::CLZ && _evmVersion < langutil::EVMVersion::osaka(); + }; auto eofIdentifiersException = [&](evmasm::Instruction _instr) -> bool { @@ -174,6 +180,7 @@ std::set> createReservedIdentifiers(langutil::EVMVersio !blobBaseFeeException(instr.second) && !mcopyException(instr.second) && !transientStorageException(instr.second) && + !clzException(instr.second) && !eofIdentifiersException(instr.second) ) reserved.emplace(name); diff --git a/scripts/test_antlr_grammar.sh b/scripts/test_antlr_grammar.sh index 5256b8128600..02e9a634f9fd 100755 --- a/scripts/test_antlr_grammar.sh +++ b/scripts/test_antlr_grammar.sh @@ -131,6 +131,8 @@ done < <( # Skipping a test with "let blobhash := ..." grep -v -E 'inlineAssembly/blobhash_pre_cancun.sol' | grep -v -E 'inlineAssembly/blobhash_pre_cancun_not_reserved.sol' | + # Skipping a test with "let clz := ..." + grep -v -E 'inlineAssembly/clz_pre_osaka.sol' | # Skipping tests with "let tstore/tload := ..." grep -v -E 'inlineAssembly/tload_tstore_not_reserved_before_cancun.sol' | # Skipping license error, unrelated to the grammar diff --git a/test/libsolidity/semanticTests/inlineAssembly/clz.sol b/test/libsolidity/semanticTests/inlineAssembly/clz.sol new file mode 100644 index 000000000000..ae4db6901832 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/clz.sol @@ -0,0 +1,25 @@ +contract C { + function f() public view returns (bytes32 ret) { + assembly { + ret := clz(0) + } + } + + function g() public view returns (bytes32 ret) { + assembly { + ret := clz(1) + } + } + + function h() public view returns (bytes32 ret) { + assembly { + ret := clz(0x4000000000000000000000000000000000000000000000000000000000000000) + } + } +} +// ==== +// EVMVersion: >=osaka +// ---- +// f() -> 256 +// g() -> 255 +// h() -> 1 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/inlineAssembly/clz_pre_osaka.sol b/test/libsolidity/semanticTests/inlineAssembly/clz_pre_osaka.sol new file mode 100644 index 000000000000..ec01f7ccbb26 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/clz_pre_osaka.sol @@ -0,0 +1,21 @@ +contract C { + function f() public pure returns (uint ret) { + assembly { + let clz := 1 + ret := clz + } + } + function g() public pure returns (uint ret) { + assembly { + function clz() -> r { + r := 1000 + } + ret := clz() + } + } +} +// ==== +// EVMVersion: 1 +// g() -> 1000 diff --git a/test/libsolidity/syntaxTests/inlineAssembly/clz.sol b/test/libsolidity/syntaxTests/inlineAssembly/clz.sol new file mode 100644 index 000000000000..720a01089298 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/clz.sol @@ -0,0 +1,10 @@ +contract C { + function f(uint256 x) public pure returns (bytes32 ret) { + assembly { + ret := clz(x) + } + } +} +// ==== +// EVMVersion: >=osaka +// ---- diff --git a/test/libsolidity/syntaxTests/inlineAssembly/clz_pre_osaka.sol b/test/libsolidity/syntaxTests/inlineAssembly/clz_pre_osaka.sol new file mode 100644 index 000000000000..8fa3d543f123 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/clz_pre_osaka.sol @@ -0,0 +1,12 @@ +contract C { + function f(uint256 x) public pure returns (bytes32 ret) { + assembly { + ret := clz(x) + } + } +} +// ==== +// EVMVersion: =prague +// ---- +// TypeError 4948: (113-116): The "clz" instruction is only available for Osaka-compatible VMs (you are currently compiling for "prague"). +// DeclarationError 8678: (106-119): Variable count for assignment to "ret" does not match number of values (1 vs. 0) diff --git a/test/libsolidity/syntaxTests/inlineAssembly/clz_reserved_osaka.sol b/test/libsolidity/syntaxTests/inlineAssembly/clz_reserved_osaka.sol new file mode 100644 index 000000000000..697b48931d9b --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/clz_reserved_osaka.sol @@ -0,0 +1,14 @@ +contract C { + function f() public pure returns (uint ret) { + assembly { + function clz() -> r { + r := 1000 + } + ret := clz() + } + } +} +// ==== +// EVMVersion: >=osaka +// ---- +// ParserError 5568: (103-106): Cannot use builtin function name "clz" as identifier name. diff --git a/test/libyul/yulInterpreterTests/clz.yul b/test/libyul/yulInterpreterTests/clz.yul new file mode 100644 index 000000000000..eab0542d2ad9 --- /dev/null +++ b/test/libyul/yulInterpreterTests/clz.yul @@ -0,0 +1,15 @@ +{ + sstore(0, clz(0)) + sstore(1, clz(1)) + sstore(2, clz(0xff)) +} +// ==== +// EVMVersion: >=osaka +// ---- +// Trace: +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000100 +// 0000000000000000000000000000000000000000000000000000000000000001: 00000000000000000000000000000000000000000000000000000000000000ff +// 0000000000000000000000000000000000000000000000000000000000000002: 00000000000000000000000000000000000000000000000000000000000000f8 +// Transient storage dump: diff --git a/test/libyul/yulSyntaxTests/clz.yul b/test/libyul/yulSyntaxTests/clz.yul new file mode 100644 index 000000000000..ee183bc2bf53 --- /dev/null +++ b/test/libyul/yulSyntaxTests/clz.yul @@ -0,0 +1,16 @@ +{ + { + let clz := 1 + } + + { + function clz() {} + clz() + } +} + +// ==== +// EVMVersion: >=osaka +// ---- +// ParserError 5568: (20-23): Cannot use builtin function name "clz" as identifier name. +// ParserError 5568: (59-62): Cannot use builtin function name "clz" as identifier name. diff --git a/test/libyul/yulSyntaxTests/clz_pre_osaka.yul b/test/libyul/yulSyntaxTests/clz_pre_osaka.yul new file mode 100644 index 000000000000..e623fd771d57 --- /dev/null +++ b/test/libyul/yulSyntaxTests/clz_pre_osaka.yul @@ -0,0 +1,14 @@ +{ + { + let clz := 1 + } + + { + function clz() {} + clz() + } +} + +// ==== +// EVMVersion: