mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add verbatim builtin.
This commit is contained in:
parent
2969bc0f3e
commit
e2d8005737
@ -1,7 +1,8 @@
|
|||||||
### 0.8.5 (unreleased)
|
### 0.8.5 (unreleased)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
|
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
|
||||||
|
* Yul: Add ``verbatim`` builtin function to inject arbitrary bytecode.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
78
docs/yul.rst
78
docs/yul.rst
@ -976,6 +976,84 @@ within one Yul subobject. If at least one ``memoryguard`` call is found in a sub
|
|||||||
the additional optimiser steps will be run on it.
|
the additional optimiser steps will be run on it.
|
||||||
|
|
||||||
|
|
||||||
|
verbatim
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
The set of ``verbatim...`` builtin functions 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_<n>i_<m>o("<data>", ...)``, 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
.. code-block:: yul
|
||||||
|
|
||||||
|
let x := calldataload(0)
|
||||||
|
let double := verbatim_1i_1o(hex"600202", x)
|
||||||
|
|
||||||
|
This code will result in a ``dup1`` opcode to retrieve ``x``
|
||||||
|
(the optimizer might directly re-use result of the
|
||||||
|
``calldataload`` opcode, though)
|
||||||
|
directly followed by ``600202``. The code is assumed to
|
||||||
|
consume the copied value of ``x`` and produce the result
|
||||||
|
on the top of the stack. The compiler then generates code
|
||||||
|
to allocate a stack slot for ``double`` and store the result there.
|
||||||
|
|
||||||
|
As with all opcodes, the arguments are arranged on the stack
|
||||||
|
with the leftmost argument on the top, while the return values
|
||||||
|
are assumed to be laid out such that the rightmost variable is
|
||||||
|
at the top of the stack.
|
||||||
|
|
||||||
|
Since ``verbatim`` can be used to generate arbitrary opcodes
|
||||||
|
or even opcodes unknown to the Solidity compiler, care has to be taken
|
||||||
|
when using ``verbatim`` together with the optimizer. Even when the
|
||||||
|
optimizer is switched off, the code generator has to determine
|
||||||
|
the stack layout, which means that e.g. using ``verbatim`` to modify
|
||||||
|
the stack height can lead to undefined behaviour.
|
||||||
|
|
||||||
|
The following is a non-exhaustive list of restrictions on
|
||||||
|
verbatim bytecode that are not checked by
|
||||||
|
the compiler. Violations of these restrictions can result in
|
||||||
|
undefined behaviour.
|
||||||
|
|
||||||
|
- Control-flow should not jump into or out of verbatim blocks,
|
||||||
|
but it can jump within the same verbatim block.
|
||||||
|
- Stack contents apart from the input and output parameters
|
||||||
|
should not be accessed.
|
||||||
|
- The stack height difference should be exactly ``m - n``
|
||||||
|
(output slots minus input slots).
|
||||||
|
- Verbatim bytecode cannot make any assumptions about the
|
||||||
|
surrounding bytecode. All required parameters have to be
|
||||||
|
passed in as stack variables.
|
||||||
|
|
||||||
|
The optimizer does not analyze verbatim bytecode and always
|
||||||
|
assumes that it modifies all aspects of state and thus can only
|
||||||
|
do very few optimizations across ``verbatim`` function calls.
|
||||||
|
|
||||||
|
The optimizer treats verbatim bytecode as an opaque block of code.
|
||||||
|
It will not split it but might move, duplicate
|
||||||
|
or combine it with identical verbatim bytecode blocks.
|
||||||
|
If a verbatim bytecode block is unreachable by the control-flow,
|
||||||
|
it can be removed.
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
During discussions about whether or not EVM improvements
|
||||||
|
might break existing smart contracts, features inside ``verbatim``
|
||||||
|
cannot receive the same consideration as those used by the Solidity
|
||||||
|
compiler itself.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To avoid confusion, all identifiers starting with the string ``verbatim`` are reserved
|
||||||
|
and cannot be used for user-defined identifiers.
|
||||||
|
|
||||||
.. _yul-object:
|
.. _yul-object:
|
||||||
|
|
||||||
Specification of Yul Object
|
Specification of Yul Object
|
||||||
|
@ -33,9 +33,11 @@
|
|||||||
|
|
||||||
#include <liblangutil/Exceptions.h>
|
#include <liblangutil/Exceptions.h>
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <range/v3/algorithm/any_of.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::evmasm;
|
using namespace solidity::evmasm;
|
||||||
@ -317,6 +319,9 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
|||||||
case PushData:
|
case PushData:
|
||||||
collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
|
collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
|
||||||
break;
|
break;
|
||||||
|
case VerbatimBytecode:
|
||||||
|
collection.append(createJsonValue("VERBATIM", sourceIndex, i.location().start, i.location().end, toHex(i.verbatimData())));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
assertThrow(false, InvalidOpcode, "");
|
assertThrow(false, InvalidOpcode, "");
|
||||||
}
|
}
|
||||||
@ -482,7 +487,9 @@ map<u256, u256> Assembly::optimiseInternal(
|
|||||||
// function types that can be stored in storage.
|
// function types that can be stored in storage.
|
||||||
AssemblyItems optimisedItems;
|
AssemblyItems optimisedItems;
|
||||||
|
|
||||||
bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem{Instruction::MSIZE}) != m_items.end());
|
bool usesMSize = ranges::any_of(m_items, [](AssemblyItem const& _i) {
|
||||||
|
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
|
||||||
|
});
|
||||||
|
|
||||||
auto iter = m_items.begin();
|
auto iter = m_items.begin();
|
||||||
while (iter != m_items.end())
|
while (iter != m_items.end())
|
||||||
@ -682,6 +689,9 @@ LinkerObject const& Assembly::assemble() const
|
|||||||
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
|
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
|
||||||
ret.bytecode.resize(ret.bytecode.size() + 32);
|
ret.bytecode.resize(ret.bytecode.size() + 32);
|
||||||
break;
|
break;
|
||||||
|
case VerbatimBytecode:
|
||||||
|
ret.bytecode += i.verbatimData();
|
||||||
|
break;
|
||||||
case AssignImmutable:
|
case AssignImmutable:
|
||||||
{
|
{
|
||||||
auto const& offsets = immutableReferencesBySub[i.data()].second;
|
auto const& offsets = immutableReferencesBySub[i.data()].second;
|
||||||
|
@ -73,6 +73,8 @@ public:
|
|||||||
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
|
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
|
||||||
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
|
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
|
||||||
|
|
||||||
|
void appendVerbatim(bytes const& _data, int _stackDifference) { append(AssemblyItem(_data, _stackDifference)); }
|
||||||
|
|
||||||
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
|
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
|
||||||
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
|
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
|
||||||
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }
|
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }
|
||||||
|
@ -91,6 +91,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
|||||||
return 1 + (3 + 32) * *m_immutableOccurrences;
|
return 1 + (3 + 32) * *m_immutableOccurrences;
|
||||||
else
|
else
|
||||||
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
|
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
|
||||||
|
case VerbatimBytecode:
|
||||||
|
return m_verbatimBytecode->second.size();
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -241,6 +243,9 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
|
|||||||
case UndefinedItem:
|
case UndefinedItem:
|
||||||
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
||||||
break;
|
break;
|
||||||
|
case VerbatimBytecode:
|
||||||
|
text = string("verbatimbytecode_") + util::toHex(m_verbatimBytecode->second);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
assertThrow(false, InvalidOpcode, "");
|
assertThrow(false, InvalidOpcode, "");
|
||||||
}
|
}
|
||||||
@ -309,6 +314,9 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
|
|||||||
case AssignImmutable:
|
case AssignImmutable:
|
||||||
_out << " AssignImmutable";
|
_out << " AssignImmutable";
|
||||||
break;
|
break;
|
||||||
|
case VerbatimBytecode:
|
||||||
|
_out << " Verbatim " << util::toHex(_item.verbatimData());
|
||||||
|
break;
|
||||||
case UndefinedItem:
|
case UndefinedItem:
|
||||||
_out << " ???";
|
_out << " ???";
|
||||||
break;
|
break;
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
namespace solidity::evmasm
|
namespace solidity::evmasm
|
||||||
{
|
{
|
||||||
|
|
||||||
enum AssemblyItemType {
|
enum AssemblyItemType
|
||||||
|
{
|
||||||
UndefinedItem,
|
UndefinedItem,
|
||||||
Operation,
|
Operation,
|
||||||
Push,
|
Push,
|
||||||
@ -47,7 +48,8 @@ enum AssemblyItemType {
|
|||||||
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
|
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
|
||||||
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
|
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
|
||||||
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
|
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
|
||||||
AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
|
AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
|
||||||
|
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
|
||||||
};
|
};
|
||||||
|
|
||||||
class Assembly;
|
class Assembly;
|
||||||
@ -75,6 +77,12 @@ public:
|
|||||||
else
|
else
|
||||||
m_data = std::make_shared<u256>(std::move(_data));
|
m_data = std::make_shared<u256>(std::move(_data));
|
||||||
}
|
}
|
||||||
|
explicit AssemblyItem(bytes const& _verbatimData, int _deposit):
|
||||||
|
m_type(VerbatimBytecode),
|
||||||
|
m_instruction{},
|
||||||
|
m_verbatimBytecode{{_deposit, _verbatimData}}
|
||||||
|
{}
|
||||||
|
|
||||||
AssemblyItem(AssemblyItem const&) = default;
|
AssemblyItem(AssemblyItem const&) = default;
|
||||||
AssemblyItem(AssemblyItem&&) = default;
|
AssemblyItem(AssemblyItem&&) = default;
|
||||||
AssemblyItem& operator=(AssemblyItem const&) = default;
|
AssemblyItem& operator=(AssemblyItem const&) = default;
|
||||||
@ -95,6 +103,8 @@ public:
|
|||||||
u256 const& data() const { assertThrow(m_type != Operation, util::Exception, ""); return *m_data; }
|
u256 const& data() const { assertThrow(m_type != Operation, util::Exception, ""); return *m_data; }
|
||||||
void setData(u256 const& _data) { assertThrow(m_type != Operation, util::Exception, ""); m_data = std::make_shared<u256>(_data); }
|
void setData(u256 const& _data) { assertThrow(m_type != Operation, util::Exception, ""); m_data = std::make_shared<u256>(_data); }
|
||||||
|
|
||||||
|
bytes const& verbatimData() const { assertThrow(m_type == VerbatimBytecode, util::Exception, ""); return m_verbatimBytecode->second; }
|
||||||
|
|
||||||
/// @returns the instruction of this item (only valid if type() == Operation)
|
/// @returns the instruction of this item (only valid if type() == Operation)
|
||||||
Instruction instruction() const { assertThrow(m_type == Operation, util::Exception, ""); return m_instruction; }
|
Instruction instruction() const { assertThrow(m_type == Operation, util::Exception, ""); return m_instruction; }
|
||||||
|
|
||||||
@ -105,6 +115,8 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
if (type() == Operation)
|
if (type() == Operation)
|
||||||
return instruction() == _other.instruction();
|
return instruction() == _other.instruction();
|
||||||
|
else if (type() == VerbatimBytecode)
|
||||||
|
return *m_verbatimBytecode == *_other.m_verbatimBytecode;
|
||||||
else
|
else
|
||||||
return data() == _other.data();
|
return data() == _other.data();
|
||||||
}
|
}
|
||||||
@ -116,6 +128,8 @@ public:
|
|||||||
return type() < _other.type();
|
return type() < _other.type();
|
||||||
else if (type() == Operation)
|
else if (type() == Operation)
|
||||||
return instruction() < _other.instruction();
|
return instruction() < _other.instruction();
|
||||||
|
else if (type() == VerbatimBytecode)
|
||||||
|
return *m_verbatimBytecode == *_other.m_verbatimBytecode;
|
||||||
else
|
else
|
||||||
return data() < _other.data();
|
return data() < _other.data();
|
||||||
}
|
}
|
||||||
@ -137,7 +151,7 @@ public:
|
|||||||
size_t bytesRequired(size_t _addressLength) const;
|
size_t bytesRequired(size_t _addressLength) const;
|
||||||
size_t arguments() const;
|
size_t arguments() const;
|
||||||
size_t returnValues() const;
|
size_t returnValues() const;
|
||||||
size_t deposit() const { return returnValues() - arguments(); }
|
size_t deposit() const { return m_type == VerbatimBytecode ? static_cast<size_t>(m_verbatimBytecode->first) : returnValues() - arguments(); }
|
||||||
|
|
||||||
/// @returns true if the assembly item can be used in a functional context.
|
/// @returns true if the assembly item can be used in a functional context.
|
||||||
bool canBeFunctional() const;
|
bool canBeFunctional() const;
|
||||||
@ -162,6 +176,8 @@ private:
|
|||||||
AssemblyItemType m_type;
|
AssemblyItemType m_type;
|
||||||
Instruction m_instruction; ///< Only valid if m_type == Operation
|
Instruction m_instruction; ///< Only valid if m_type == Operation
|
||||||
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
|
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
|
||||||
|
/// If m_type == VerbatimBytecode, this holds stack difference and verbatim bytecode.
|
||||||
|
std::optional<std::pair<int, bytes>> m_verbatimBytecode;
|
||||||
langutil::SourceLocation m_location;
|
langutil::SourceLocation m_location;
|
||||||
JumpType m_jumpType = JumpType::Ordinary;
|
JumpType m_jumpType = JumpType::Ordinary;
|
||||||
/// Pushed value for operations with data to be determined during assembly stage,
|
/// Pushed value for operations with data to be determined during assembly stage,
|
||||||
|
@ -38,6 +38,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
|
|||||||
case Tag:
|
case Tag:
|
||||||
case PushDeployTimeAddress:
|
case PushDeployTimeAddress:
|
||||||
case AssignImmutable:
|
case AssignImmutable:
|
||||||
|
case VerbatimBytecode:
|
||||||
return true;
|
return true;
|
||||||
case Push:
|
case Push:
|
||||||
case PushString:
|
case PushString:
|
||||||
@ -164,6 +165,8 @@ bool SemanticInformation::reverts(Instruction _instruction)
|
|||||||
|
|
||||||
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
|
assertThrow(_item.type() != VerbatimBytecode, AssemblyException, "");
|
||||||
|
|
||||||
if (_item.type() != Operation)
|
if (_item.type() != Operation)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -369,16 +369,26 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
);
|
);
|
||||||
else if (*literalArgumentKind == LiteralKind::String)
|
else if (*literalArgumentKind == LiteralKind::String)
|
||||||
{
|
{
|
||||||
if (
|
string functionName = _funCall.functionName.name.str();
|
||||||
_funCall.functionName.name.str() == "datasize" ||
|
if (functionName == "datasize" || functionName == "dataoffset")
|
||||||
_funCall.functionName.name.str() == "dataoffset"
|
{
|
||||||
)
|
|
||||||
if (!m_dataNames.count(get<Literal>(arg).value))
|
if (!m_dataNames.count(get<Literal>(arg).value))
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
3517_error,
|
3517_error,
|
||||||
get<Literal>(arg).location,
|
get<Literal>(arg).location,
|
||||||
"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"."
|
"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"."
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else if (functionName.substr(0, "verbatim_"s.size()) == "verbatim_")
|
||||||
|
{
|
||||||
|
if (get<Literal>(arg).value.empty())
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
1844_error,
|
||||||
|
get<Literal>(arg).location,
|
||||||
|
"The \"verbatim_*\" builtins cannot be used with empty bytecode."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
argTypes.emplace_back(expectUnlimitedStringLiteral(get<Literal>(arg)));
|
argTypes.emplace_back(expectUnlimitedStringLiteral(get<Literal>(arg)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ struct BuiltinFunction
|
|||||||
std::vector<Type> returns;
|
std::vector<Type> returns;
|
||||||
SideEffects sideEffects;
|
SideEffects sideEffects;
|
||||||
ControlFlowSideEffects controlFlowSideEffects;
|
ControlFlowSideEffects controlFlowSideEffects;
|
||||||
/// If true, this is the msize instruction.
|
/// If true, this is the msize instruction or might contain it.
|
||||||
bool isMSize = false;
|
bool isMSize = false;
|
||||||
/// Must be empty or the same length as the arguments.
|
/// Must be empty or the same length as the arguments.
|
||||||
/// If set at index i, the i'th argument has to be a literal which means it can't be moved to variables.
|
/// If set at index i, the i'th argument has to be a literal which means it can't be moved to variables.
|
||||||
|
@ -77,6 +77,9 @@ public:
|
|||||||
/// Currently, we assume that the value is always a 20 byte number.
|
/// Currently, we assume that the value is always a 20 byte number.
|
||||||
virtual void appendLinkerSymbol(std::string const& _name) = 0;
|
virtual void appendLinkerSymbol(std::string const& _name) = 0;
|
||||||
|
|
||||||
|
/// Append raw bytes that stay untouched by the optimizer.
|
||||||
|
virtual void appendVerbatim(bytes const& _data, int _stackDifference) = 0;
|
||||||
|
|
||||||
/// Append a jump instruction.
|
/// Append a jump instruction.
|
||||||
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
||||||
/// This is helpful to stack height analysis if there is no continuing control flow.
|
/// This is helpful to stack height analysis if there is no continuing control flow.
|
||||||
|
@ -99,6 +99,11 @@ void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol)
|
|||||||
m_assembly.appendLibraryAddress(_linkerSymbol);
|
m_assembly.appendLibraryAddress(_linkerSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EthAssemblyAdapter::appendVerbatim(bytes const& _data, int _stackDifference)
|
||||||
|
{
|
||||||
|
m_assembly.appendVerbatim(_data, _stackDifference);
|
||||||
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendJump(int _stackDiffAfter, JumpType _jumpType)
|
void EthAssemblyAdapter::appendJump(int _stackDiffAfter, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
appendJumpInstruction(evmasm::Instruction::JUMP, _jumpType);
|
appendJumpInstruction(evmasm::Instruction::JUMP, _jumpType);
|
||||||
|
@ -50,6 +50,7 @@ public:
|
|||||||
size_t newLabelId() override;
|
size_t newLabelId() override;
|
||||||
size_t namedLabel(std::string const& _name) override;
|
size_t namedLabel(std::string const& _name) override;
|
||||||
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
|
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
|
||||||
|
void appendVerbatim(bytes const& _data, int _stackDifference) override;
|
||||||
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||||
|
@ -34,6 +34,9 @@
|
|||||||
#include <liblangutil/Exceptions.h>
|
#include <liblangutil/Exceptions.h>
|
||||||
|
|
||||||
#include <range/v3/view/reverse.hpp>
|
#include <range/v3/view/reverse.hpp>
|
||||||
|
#include <range/v3/view/tail.hpp>
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
@ -280,6 +283,12 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
|||||||
return builtins;
|
return builtins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regex const& verbatimPattern()
|
||||||
|
{
|
||||||
|
regex static const pattern{"verbatim_([1-9]?[0-9])i_([1-9]?[0-9])o"};
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -293,6 +302,12 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess):
|
|||||||
|
|
||||||
BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const
|
BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const
|
||||||
{
|
{
|
||||||
|
if (m_objectAccess)
|
||||||
|
{
|
||||||
|
smatch match;
|
||||||
|
if (regex_match(_name.str(), match, verbatimPattern()))
|
||||||
|
return verbatimFunction(stoul(match[1]), stoul(match[2]));
|
||||||
|
}
|
||||||
auto it = m_functions.find(_name);
|
auto it = m_functions.find(_name);
|
||||||
if (it != m_functions.end())
|
if (it != m_functions.end())
|
||||||
return &it->second;
|
return &it->second;
|
||||||
@ -302,6 +317,9 @@ BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const
|
|||||||
|
|
||||||
bool EVMDialect::reservedIdentifier(YulString _name) const
|
bool EVMDialect::reservedIdentifier(YulString _name) const
|
||||||
{
|
{
|
||||||
|
if (m_objectAccess)
|
||||||
|
if (_name.str().substr(0, "verbatim"s.size()) == "verbatim")
|
||||||
|
return true;
|
||||||
return m_reserved.count(_name) != 0;
|
return m_reserved.count(_name) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,6 +360,38 @@ SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instructio
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuiltinFunctionForEVM const* EVMDialect::verbatimFunction(size_t _arguments, size_t _returnVariables) const
|
||||||
|
{
|
||||||
|
pair<size_t, size_t> key{_arguments, _returnVariables};
|
||||||
|
shared_ptr<BuiltinFunctionForEVM const>& function = m_verbatimFunctions[key];
|
||||||
|
if (!function)
|
||||||
|
{
|
||||||
|
BuiltinFunctionForEVM builtinFunction = createFunction(
|
||||||
|
"verbatim_" + to_string(_arguments) + "i_" + to_string(_returnVariables) + "o",
|
||||||
|
1 + _arguments,
|
||||||
|
_returnVariables,
|
||||||
|
SideEffects::worst(),
|
||||||
|
vector<optional<LiteralKind>>{LiteralKind::String} + vector<optional<LiteralKind>>(_arguments),
|
||||||
|
[=](
|
||||||
|
FunctionCall const& _call,
|
||||||
|
AbstractAssembly& _assembly,
|
||||||
|
BuiltinContext&,
|
||||||
|
std::function<void(Expression const&)> _visitExpression
|
||||||
|
) {
|
||||||
|
yulAssert(_call.arguments.size() == (1 + _arguments), "");
|
||||||
|
for (Expression const& arg: _call.arguments | ranges::views::tail | ranges::views::reverse)
|
||||||
|
_visitExpression(arg);
|
||||||
|
Expression const& bytecode = _call.arguments.front();
|
||||||
|
int diff = static_cast<int>(_returnVariables) - static_cast<int>(_arguments);
|
||||||
|
_assembly.appendVerbatim(asBytes(std::get<Literal>(bytecode).value.str()), diff);
|
||||||
|
}
|
||||||
|
).second;
|
||||||
|
builtinFunction.isMSize = true;
|
||||||
|
function = make_shared<BuiltinFunctionForEVM const>(move(builtinFunction));
|
||||||
|
}
|
||||||
|
return function.get();
|
||||||
|
}
|
||||||
|
|
||||||
EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess):
|
EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess):
|
||||||
EVMDialect(_evmVersion, _objectAccess)
|
EVMDialect(_evmVersion, _objectAccess)
|
||||||
{
|
{
|
||||||
|
@ -93,9 +93,12 @@ struct EVMDialect: public Dialect
|
|||||||
static SideEffects sideEffectsOfInstruction(evmasm::Instruction _instruction);
|
static SideEffects sideEffectsOfInstruction(evmasm::Instruction _instruction);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
BuiltinFunctionForEVM const* verbatimFunction(size_t _arguments, size_t _returnVariables) const;
|
||||||
|
|
||||||
bool const m_objectAccess;
|
bool const m_objectAccess;
|
||||||
langutil::EVMVersion const m_evmVersion;
|
langutil::EVMVersion const m_evmVersion;
|
||||||
std::map<YulString, BuiltinFunctionForEVM> m_functions;
|
std::map<YulString, BuiltinFunctionForEVM> m_functions;
|
||||||
|
std::map<std::pair<size_t, size_t>, std::shared_ptr<BuiltinFunctionForEVM const>> mutable m_verbatimFunctions;
|
||||||
std::set<YulString> m_reserved;
|
std::set<YulString> m_reserved;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,6 +69,11 @@ void NoOutputAssembly::appendLinkerSymbol(string const&)
|
|||||||
yulAssert(false, "Linker symbols not yet implemented.");
|
yulAssert(false, "Linker symbols not yet implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NoOutputAssembly::appendVerbatim(bytes const&, int _stackDifference)
|
||||||
|
{
|
||||||
|
m_stackHeight += _stackDifference;
|
||||||
|
}
|
||||||
|
|
||||||
void NoOutputAssembly::appendJump(int _stackDiffAfter, JumpType)
|
void NoOutputAssembly::appendJump(int _stackDiffAfter, JumpType)
|
||||||
{
|
{
|
||||||
appendInstruction(evmasm::Instruction::JUMP);
|
appendInstruction(evmasm::Instruction::JUMP);
|
||||||
|
@ -58,6 +58,7 @@ public:
|
|||||||
LabelID newLabelId() override;
|
LabelID newLabelId() override;
|
||||||
LabelID namedLabel(std::string const& _name) override;
|
LabelID namedLabel(std::string const& _name) override;
|
||||||
void appendLinkerSymbol(std::string const& _name) override;
|
void appendLinkerSymbol(std::string const& _name) override;
|
||||||
|
void appendVerbatim(bytes const& _data, int _stackDifference) override;
|
||||||
|
|
||||||
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
|
@ -131,7 +131,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that can be used to find out if certain code contains the MSize instruction.
|
* Class that can be used to find out if certain code contains the MSize instruction
|
||||||
|
* or a verbatim bytecode builtin (which is always assumed that it could contain MSize).
|
||||||
*
|
*
|
||||||
* Note that this is a purely syntactic property meaning that even if this is false,
|
* Note that this is a purely syntactic property meaning that even if this is false,
|
||||||
* the code can still contain calls to functions that contain the msize instruction.
|
* the code can still contain calls to functions that contain the msize instruction.
|
||||||
|
1
test/cmdlineTests/yul_verbatim/args
Normal file
1
test/cmdlineTests/yul_verbatim/args
Normal file
@ -0,0 +1 @@
|
|||||||
|
--strict-assembly
|
1
test/cmdlineTests/yul_verbatim/err
Normal file
1
test/cmdlineTests/yul_verbatim/err
Normal file
@ -0,0 +1 @@
|
|||||||
|
Warning: Yul is still experimental. Please use the output with care.
|
1
test/cmdlineTests/yul_verbatim/exit
Normal file
1
test/cmdlineTests/yul_verbatim/exit
Normal file
@ -0,0 +1 @@
|
|||||||
|
0
|
11
test/cmdlineTests/yul_verbatim/input.yul
Normal file
11
test/cmdlineTests/yul_verbatim/input.yul
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
let x := 2
|
||||||
|
let y := sub(x, 2)
|
||||||
|
let t := verbatim_2i_1o("abc", x, y)
|
||||||
|
sstore(t, x)
|
||||||
|
let r := verbatim_0i_1o("def")
|
||||||
|
verbatim_0i_0o("xyz")
|
||||||
|
// more than 32 bytes
|
||||||
|
verbatim_0i_0o(hex"01020304050607090001020304050607090001020304050607090001020102030405060709000102030405060709000102030405060709000102")
|
||||||
|
r := 9
|
||||||
|
}
|
56
test/cmdlineTests/yul_verbatim/output
Normal file
56
test/cmdlineTests/yul_verbatim/output
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
======= yul_verbatim/input.yul (EVM) =======
|
||||||
|
|
||||||
|
Pretty printed source:
|
||||||
|
object "object" {
|
||||||
|
code {
|
||||||
|
let x := 2
|
||||||
|
let y := sub(x, 2)
|
||||||
|
let t := verbatim_2i_1o("abc", x, y)
|
||||||
|
sstore(t, x)
|
||||||
|
let r := verbatim_0i_1o("def")
|
||||||
|
verbatim_0i_0o("xyz")
|
||||||
|
verbatim_0i_0o("\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02")
|
||||||
|
r := 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Binary representation:
|
||||||
|
600260028103808261626382815564656678797a010203040506070900010203040506070900010203040506070900010201020304050607090001020304050607090001020304050607090001026009905050505050
|
||||||
|
|
||||||
|
Text representation:
|
||||||
|
/* "yul_verbatim/input.yul":15:16 */
|
||||||
|
0x02
|
||||||
|
/* "yul_verbatim/input.yul":37:38 */
|
||||||
|
0x02
|
||||||
|
/* "yul_verbatim/input.yul":34:35 */
|
||||||
|
dup2
|
||||||
|
/* "yul_verbatim/input.yul":30:39 */
|
||||||
|
sub
|
||||||
|
/* "yul_verbatim/input.yul":78:79 */
|
||||||
|
dup1
|
||||||
|
/* "yul_verbatim/input.yul":75:76 */
|
||||||
|
dup3
|
||||||
|
verbatimbytecode_616263
|
||||||
|
/* "yul_verbatim/input.yul":95:96 */
|
||||||
|
dup3
|
||||||
|
/* "yul_verbatim/input.yul":92:93 */
|
||||||
|
dup2
|
||||||
|
/* "yul_verbatim/input.yul":85:97 */
|
||||||
|
sstore
|
||||||
|
verbatimbytecode_646566
|
||||||
|
/* "yul_verbatim/input.yul":137:158 */
|
||||||
|
verbatimbytecode_78797a
|
||||||
|
/* "yul_verbatim/input.yul":189:326 */
|
||||||
|
verbatimbytecode_01020304050607090001020304050607090001020304050607090001020102030405060709000102030405060709000102030405060709000102
|
||||||
|
/* "yul_verbatim/input.yul":336:337 */
|
||||||
|
0x09
|
||||||
|
/* "yul_verbatim/input.yul":331:337 */
|
||||||
|
swap1
|
||||||
|
pop
|
||||||
|
/* "yul_verbatim/input.yul":0:339 */
|
||||||
|
pop
|
||||||
|
pop
|
||||||
|
pop
|
||||||
|
pop
|
1
test/cmdlineTests/yul_verbatim_msize/args
Normal file
1
test/cmdlineTests/yul_verbatim_msize/args
Normal file
@ -0,0 +1 @@
|
|||||||
|
--strict-assembly --optimize
|
1
test/cmdlineTests/yul_verbatim_msize/err
Normal file
1
test/cmdlineTests/yul_verbatim_msize/err
Normal file
@ -0,0 +1 @@
|
|||||||
|
Warning: Yul is still experimental. Please use the output with care.
|
1
test/cmdlineTests/yul_verbatim_msize/exit
Normal file
1
test/cmdlineTests/yul_verbatim_msize/exit
Normal file
@ -0,0 +1 @@
|
|||||||
|
0
|
7
test/cmdlineTests/yul_verbatim_msize/input.yul
Normal file
7
test/cmdlineTests/yul_verbatim_msize/input.yul
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// The optimizer assumes verbatim could contain msize,
|
||||||
|
// so it cannot optimize the mload away.
|
||||||
|
let x := mload(0x2000)
|
||||||
|
verbatim_0i_0o("")
|
||||||
|
sstore(0, 2)
|
||||||
|
}
|
32
test/cmdlineTests/yul_verbatim_msize/output
Normal file
32
test/cmdlineTests/yul_verbatim_msize/output
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
======= yul_verbatim_msize/input.yul (EVM) =======
|
||||||
|
|
||||||
|
Pretty printed source:
|
||||||
|
object "object" {
|
||||||
|
code {
|
||||||
|
{
|
||||||
|
pop(mload(0x2000))
|
||||||
|
verbatim_0i_0o("aa")
|
||||||
|
sstore(0, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Binary representation:
|
||||||
|
612000515061616002600055
|
||||||
|
|
||||||
|
Text representation:
|
||||||
|
/* "yul_verbatim_msize/input.yul":125:131 */
|
||||||
|
0x2000
|
||||||
|
/* "yul_verbatim_msize/input.yul":119:132 */
|
||||||
|
mload
|
||||||
|
pop
|
||||||
|
/* "yul_verbatim_msize/input.yul":137:157 */
|
||||||
|
verbatimbytecode_6161
|
||||||
|
/* "yul_verbatim_msize/input.yul":172:173 */
|
||||||
|
0x02
|
||||||
|
/* "yul_verbatim_msize/input.yul":169:170 */
|
||||||
|
0x00
|
||||||
|
/* "yul_verbatim_msize/input.yul":162:174 */
|
||||||
|
sstore
|
@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
#include <range/v3/algorithm/any_of.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -67,7 +69,9 @@ namespace
|
|||||||
{
|
{
|
||||||
AssemblyItems input = addDummyLocations(_input);
|
AssemblyItems input = addDummyLocations(_input);
|
||||||
|
|
||||||
bool usesMsize = (find(_input.begin(), _input.end(), AssemblyItem{Instruction::MSIZE}) != _input.end());
|
bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) {
|
||||||
|
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
|
||||||
|
});
|
||||||
evmasm::CommonSubexpressionEliminator cse(_state);
|
evmasm::CommonSubexpressionEliminator cse(_state);
|
||||||
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
|
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
|
||||||
AssemblyItems output = cse.getOptimizedItems();
|
AssemblyItems output = cse.getOptimizedItems();
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
contract C {
|
||||||
|
function f() pure public {
|
||||||
|
assembly {
|
||||||
|
let x := verbatim_1o(hex"6001")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// optimize-yul: true
|
||||||
|
// ----
|
||||||
|
// DeclarationError 4619: (84-95): Function "verbatim_1o" not found.
|
||||||
|
// DeclarationError 3812: (75-106): Variable count mismatch for declaration of "x": 1 variables and 0 values.
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
verbatim_0i_0o(hex"")
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// dialect: evm
|
||||||
|
// ----
|
||||||
|
// TypeError 1844: (21-26): The "verbatim_*" builtins cannot be used with empty bytecode.
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
let x := verbatim_01i_1o(hex"aa", 0)
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// DeclarationError 4619: (15-30): Function "verbatim_01i_1o" not found.
|
||||||
|
// DeclarationError 3812: (6-42): Variable count mismatch for declaration of "x": 1 variables and 0 values.
|
6
test/libyul/yulSyntaxTests/invalid/verbatim_reserved.yul
Normal file
6
test/libyul/yulSyntaxTests/invalid/verbatim_reserved.yul
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
let verbatim := 2
|
||||||
|
let verbatim_1i_2o := 3
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// ParserError 5568: (32-46): Cannot use builtin function name "verbatim_1i_2o" as identifier name.
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
let verbatim := 2
|
||||||
|
let verbatim_abc := 3
|
||||||
|
function verbatim_fun() {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// DeclarationError 5017: (10-18): The identifier "verbatim" is reserved and can not be used.
|
||||||
|
// DeclarationError 5017: (32-44): The identifier "verbatim_abc" is reserved and can not be used.
|
||||||
|
// DeclarationError 5017: (54-80): The identifier "verbatim_fun" is reserved and can not be used.
|
5
test/libyul/yulSyntaxTests/verbatim_leading_zero.yul
Normal file
5
test/libyul/yulSyntaxTests/verbatim_leading_zero.yul
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
let verbatim_01i_02o := 2
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// DeclarationError 5017: (10-26): The identifier "verbatim_01i_02o" is reserved and can not be used.
|
Loading…
Reference in New Issue
Block a user