diff --git a/docs/yul.rst b/docs/yul.rst index c81c9562a58c..6095d2f683ed 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -589,7 +589,7 @@ declaration. Functions can be referenced already before their declaration (if they are visible). As an exception to the general scoping rule, the scope of the "init" part of the for-loop -(the first block) extends across all other parts of the for loop. +(the first block) extends across all other parts of the for-loop. This means that variables (and functions) declared in the init part (but not inside a block inside the init part) are visible in all other parts of the for-loop. @@ -1050,15 +1050,28 @@ the additional optimiser steps will be run on it. verbatim ^^^^^^^^ -The set of ``verbatim...`` builtin functions lets you create bytecode for opcodes +The ``verbatim`` builtin function lets you create bytecode for opcodes that are not known to the Yul compiler. It also allows you to create bytecode sequences that will not be modified by the optimizer. -The functions are ``verbatim_i_o("", ...)``, where +This function can be used in two ways: -- ``n`` is a decimal between 0 and 99 that specifies the number of input stack slots / variables -- ``m`` is a decimal between 0 and 99 that specifies the number of output stack slots / variables -- ``data`` is a string literal that contains the sequence of bytes +1. (Recommended) Using the newer syntax ``verbatim(n, m, "", ...)``, where: + + - ``n`` is a literal number between 0 and 99 that specifies the number of input stack slots / variables + - ``m`` is a literal number between 0 and 99 that specifies the number of output stack slots / variables + - ``data`` is a string literal that contains the sequence of bytes + +2. (Legacy) Using the older syntax ``verbatim_i_o("", ...)``, where: + + - ``n`` is a decimal between 0 and 99 that specifies the number of input stack slots / variables + - ``m`` is a decimal between 0 and 99 that specifies the number of output stack slots / variables + - ``data`` is a string literal that contains the sequence of bytes + +.. note:: + + The legacy ``verbatim_i_o`` functions are deprecated and will be removed in a future version. + Please use the new ``verbatim`` function. If you for example want to define a function that multiplies the input by two, without the optimizer touching the constant two, you can use @@ -1066,7 +1079,7 @@ by two, without the optimizer touching the constant two, you can use .. code-block:: yul let x := calldataload(0) - let double := verbatim_1i_1o(hex"600202", x) + let double := verbatim(1, 1, hex"600202", x) This code will result in a ``dup1`` opcode to retrieve ``x`` (the optimizer might directly reuse result of the @@ -1344,13 +1357,6 @@ Complete ERC20 Example executeTransfer(from, to, amount) } - function executeTransfer(from, to, amount) { - revertIfZeroAddress(to) - deductFromBalance(from, amount) - addToBalance(to, amount) - emitTransfer(from, to, amount) - } - /* ---------- calldata decoding functions ----------- */ function selector() -> s { diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index d68ef16348b1..ac7871ed26f4 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -250,6 +250,46 @@ std::vector> createBuiltins(langutil::EVMVe return createFunction(_name, _params, _returns, _sideEffects, _controlFlowSideEffects, std::move(_literalArguments), std::move(_generateCode)); }; + builtins.emplace_back(createIfObjectAccess( + "verbatim", + 3, + 0, + SideEffects::worst(), + ControlFlowSideEffects::worst(), // Worst control flow side effects because verbatim can do anything. + {LiteralKind::Number, LiteralKind::Number, LiteralKind::String}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& + ) { + yulAssert(_call.arguments.size() == 3, ""); + + // Get the number of inputs from the first argument + Literal const* argsLiteral = std::get_if(&_call.arguments[0]); + yulAssert(argsLiteral, "First argument must be a literal"); + size_t arguments = static_cast(argsLiteral->value.value()); + + // Get the number of outputs from the second argument + Literal const* retsLiteral = std::get_if(&_call.arguments[1]); + yulAssert(retsLiteral, "Second argument must be a literal"); + size_t returnVariables = static_cast(retsLiteral->value.value()); + + // Verify that arguments and returnVariables are in the allowed range + yulAssert(arguments <= EVMDialect::verbatimMaxInputSlots, "Too many verbatim input arguments"); + yulAssert(returnVariables <= EVMDialect::verbatimMaxOutputSlots, "Too many verbatim return values"); + + // Get the bytecode from the third argument + Literal const* bytecodeStr = std::get_if(&_call.arguments[2]); + yulAssert(bytecodeStr, "Third argument must be a literal"); + + _assembly.appendVerbatim( + asBytes(formatLiteral(*bytecodeStr)), + arguments, + returnVariables + ); + } + )); + builtins.emplace_back(createIfObjectAccess("linkersymbol", 1, 1, SideEffects{}, ControlFlowSideEffects{}, {LiteralKind::String}, []( FunctionCall const& _call, AbstractAssembly& _assembly, diff --git a/test/cmdlineTests/yul_verbatim_single/input.yul b/test/cmdlineTests/yul_verbatim_single/input.yul new file mode 100644 index 000000000000..60d768744c95 --- /dev/null +++ b/test/cmdlineTests/yul_verbatim_single/input.yul @@ -0,0 +1,11 @@ +{ + let x := 2 + let y := sub(x, 2) + // Using the new verbatim builtin with literal args instead of verbatim_2i_1o + let r := verbatim(0, 1, "def") // No input arguments, 1 output value + let t := verbatim(2, 1, "abc", x, y) // 2 input arguments, 1 output value + sstore(t, x) + verbatim(0, 0, "xyz") // No input arguments, no output values + // more than 32 bytes + verbatim(0, 0, hex"01020304050607090001020304050607090001020304050607090001020102030405060709000102030405060709000102030405060709000102") +} diff --git a/test/cmdlineTests/yul_verbatim_single/output.json b/test/cmdlineTests/yul_verbatim_single/output.json new file mode 100644 index 000000000000..a4088d8f40b0 --- /dev/null +++ b/test/cmdlineTests/yul_verbatim_single/output.json @@ -0,0 +1,12 @@ +{ + "contracts": {}, + "sourceList": [ + "input.yul" + ], + "sources": { + "input.yul": { + "id": 0 + } + }, + "version": "" +} diff --git a/test/libyul/yulSyntaxTests/verbatim_single.yul b/test/libyul/yulSyntaxTests/verbatim_single.yul new file mode 100644 index 000000000000..4d5e851cf454 --- /dev/null +++ b/test/libyul/yulSyntaxTests/verbatim_single.yul @@ -0,0 +1,7 @@ +{ + let x := verbatim(2, 1, "abc") + verbatim(0, 0, "xyz") +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/verbatim_single_insufficient_args.yul b/test/libyul/yulSyntaxTests/verbatim_single_insufficient_args.yul new file mode 100644 index 000000000000..6e1b1902d4da --- /dev/null +++ b/test/libyul/yulSyntaxTests/verbatim_single_insufficient_args.yul @@ -0,0 +1,9 @@ +{ + // Test what happens when we provide fewer function arguments than specified in the first parameter + let x := 2 + let t := verbatim(2, 1, "abc", x) // Expecting 2 inputs but only given 1 +} +// ==== +// dialect: evm +// ---- +// TypeError 4323: (121-142): Function expects 5 arguments but got 4. diff --git a/test/libyul/yulSyntaxTests/verbatim_single_invalid_args.yul b/test/libyul/yulSyntaxTests/verbatim_single_invalid_args.yul new file mode 100644 index 000000000000..ebd75d8045cc --- /dev/null +++ b/test/libyul/yulSyntaxTests/verbatim_single_invalid_args.yul @@ -0,0 +1,11 @@ +{ + // Using variables as arguments which should be literals + let n := 2 + let m := 1 + let c := "abc" + let x := verbatim(n, m, c) +} +// ==== +// dialect: evm +// ---- +// TypeError 9114: (92-112): Function expects direct literals as arguments. diff --git a/test/libyul/yulSyntaxTests/verbatim_single_invalid_range.yul b/test/libyul/yulSyntaxTests/verbatim_single_invalid_range.yul new file mode 100644 index 000000000000..85a97c328873 --- /dev/null +++ b/test/libyul/yulSyntaxTests/verbatim_single_invalid_range.yul @@ -0,0 +1,7 @@ +{ + let x := verbatim(100, 1, "abc") +} +// ==== +// dialect: evm +// ---- +// Type of call argument too large (currently disallowed). diff --git a/test/libyul/yulSyntaxTests/verbatim_single_valid.yul b/test/libyul/yulSyntaxTests/verbatim_single_valid.yul new file mode 100644 index 000000000000..f31e4d723b45 --- /dev/null +++ b/test/libyul/yulSyntaxTests/verbatim_single_valid.yul @@ -0,0 +1,11 @@ +{ + // Test that we can provide the correct number of arguments + let x := 2 + let y := 3 + let t := verbatim(2, 1, "abc", x, y) // Correct: 2 inputs as specified + let z := verbatim(0, 1, "def") // Correct: 0 inputs as specified + verbatim(0, 0, "xyz") // Correct: 0 inputs, 0 outputs +} +// ==== +// dialect: evm +// ----