mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7650 from ethereum/develop
Merge develop into develop_060
This commit is contained in:
commit
21e65076b3
@ -40,6 +40,8 @@ Compiler Features:
|
||||
* Code Generator: Use SELFBALANCE for ``address(this).balance`` if using Istanbul EVM
|
||||
* SMTChecker: Add break/continue support to the CHC engine.
|
||||
* SMTChecker: Support assignments to multi-dimensional arrays and mappings.
|
||||
* SMTChecker: Support inheritance and function overriding.
|
||||
* EWasm: Experimental EWasm binary output.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -60,6 +60,8 @@ Please follow the
|
||||
[Developers Guide](https://solidity.readthedocs.io/en/latest/contributing.html)
|
||||
if you want to help.
|
||||
|
||||
You can find our current feature and bug priorities for forthcoming releases [in the projects section](https://github.com/ethereum/solidity/projects).
|
||||
|
||||
## Maintainers
|
||||
* [@axic](https://github.com/axic)
|
||||
* [@chriseth](https://github.com/chriseth)
|
||||
|
@ -4,7 +4,8 @@
|
||||
Mapping Types
|
||||
=============
|
||||
|
||||
You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``.
|
||||
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
|
||||
are declared as a mapping type using the syntax ``mapping (_KeyType => _ValueType) _VariableModifiers _VariableName``.
|
||||
The ``_KeyType`` can be any elementary type. This means it can be any of
|
||||
the built-in value types plus ``bytes`` and ``string``. User-defined
|
||||
or complex types like contract types, enums, mappings, structs and any array type
|
||||
@ -27,11 +28,16 @@ They cannot be used as parameters or return parameters
|
||||
of contract functions that are publicly visible.
|
||||
|
||||
You can mark state variables of mapping type as ``public`` and Solidity creates a
|
||||
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a
|
||||
parameter for the getter. If ``_ValueType`` is a value type or a struct,
|
||||
the getter returns ``_ValueType``.
|
||||
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a parameter for the getter.
|
||||
If ``_ValueType`` is a value type or a struct, the getter returns ``_ValueType``.
|
||||
If ``_ValueType`` is an array or a mapping, the getter has one parameter for
|
||||
each ``_KeyType``, recursively. For example with a mapping:
|
||||
each ``_KeyType``, recursively.
|
||||
|
||||
In the example below, the ``MappingExample`` contract defines a public ``balances``
|
||||
mapping, with the key type an ``address``, and a value type a ``uint``, mapping
|
||||
an Ethereum address to an unsigned integer value. As ``uint`` is a value type, the getter
|
||||
returns a value that matches the type, which you can see in the ``MappingUser``
|
||||
contract that returns the value at the specified address.
|
||||
|
||||
::
|
||||
|
||||
@ -53,7 +59,146 @@ each ``_KeyType``, recursively. For example with a mapping:
|
||||
}
|
||||
}
|
||||
|
||||
The example below is a simplified version of an `ERC20 token <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol>`_.
|
||||
``_allowances`` is an example of a mapping type inside another mapping type.
|
||||
The example below uses ``_allowances`` to record the amount someone else is allowed to withdraw from your account.
|
||||
|
||||
.. note::
|
||||
Mappings are not iterable, but it is possible to implement a data structure
|
||||
on top of them. For an example, see `iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_.
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract MappingExample {
|
||||
|
||||
mapping (address => uint256) private _balances;
|
||||
mapping (address => mapping (address => uint256)) private _allowances;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
|
||||
function allowance(address owner, address spender) public view returns (uint256) {
|
||||
return _allowances[owner][spender];
|
||||
}
|
||||
|
||||
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
|
||||
_transfer(sender, recipient, amount);
|
||||
approve(sender, msg.sender, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function approve(address owner, address spender, uint256 amount) public returns (bool) {
|
||||
require(owner != address(0), "ERC20: approve from the zero address");
|
||||
require(spender != address(0), "ERC20: approve to the zero address");
|
||||
|
||||
_allowances[owner][spender] = amount;
|
||||
emit Approval(owner, spender, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function _transfer(address sender, address recipient, uint256 amount) internal {
|
||||
require(sender != address(0), "ERC20: transfer from the zero address");
|
||||
require(recipient != address(0), "ERC20: transfer to the zero address");
|
||||
|
||||
_balances[sender] -= amount;
|
||||
_balances[recipient] += amount;
|
||||
emit Transfer(sender, recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. index:: !iterable mappings
|
||||
.. _iterable-mappings:
|
||||
|
||||
Iterable Mappings
|
||||
-----------------
|
||||
|
||||
Mappings are not iterable, but it is possible to implement a data structure on
|
||||
top of them and iterate over that. For example, the code below implements an
|
||||
``IterableMapping`` library that the ``User`` contract then adds data too, and
|
||||
the ``sum`` function iterates over to sum all the values.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
library IterableMapping {
|
||||
|
||||
struct itmap {
|
||||
mapping(uint => IndexValue) data;
|
||||
KeyFlag[] keys;
|
||||
uint size;
|
||||
}
|
||||
|
||||
struct IndexValue { uint keyIndex; uint value; }
|
||||
struct KeyFlag { uint key; bool deleted; }
|
||||
|
||||
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
|
||||
uint keyIndex = self.data[key].keyIndex;
|
||||
self.data[key].value = value;
|
||||
if (keyIndex > 0)
|
||||
return true;
|
||||
else {
|
||||
keyIndex = self.keys.length++;
|
||||
self.data[key].keyIndex = keyIndex + 1;
|
||||
self.keys[keyIndex].key = key;
|
||||
self.size++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function remove(itmap storage self, uint key) internal returns (bool success) {
|
||||
uint keyIndex = self.data[key].keyIndex;
|
||||
if (keyIndex == 0)
|
||||
return false;
|
||||
delete self.data[key];
|
||||
self.keys[keyIndex - 1].deleted = true;
|
||||
self.size --;
|
||||
}
|
||||
|
||||
function contains(itmap storage self, uint key) internal view returns (bool) {
|
||||
return self.data[key].keyIndex > 0;
|
||||
}
|
||||
|
||||
function iterate_start(itmap storage self) internal view returns (uint keyIndex) {
|
||||
return iterate_next(self, uint(-1));
|
||||
}
|
||||
|
||||
function iterate_valid(itmap storage self, uint keyIndex) internal view returns (bool) {
|
||||
return keyIndex < self.keys.length;
|
||||
}
|
||||
|
||||
function iterate_next(itmap storage self, uint keyIndex) internal view returns (uint r_keyIndex) {
|
||||
keyIndex++;
|
||||
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
|
||||
keyIndex++;
|
||||
return keyIndex;
|
||||
}
|
||||
|
||||
function iterate_get(itmap storage self, uint keyIndex) internal view returns (uint key, uint value) {
|
||||
key = self.keys[keyIndex].key;
|
||||
value = self.data[key].value;
|
||||
}
|
||||
}
|
||||
|
||||
// How to use it
|
||||
contract User {
|
||||
// Just a struct holding our data.
|
||||
IterableMapping.itmap data;
|
||||
|
||||
// Insert something
|
||||
function insert(uint k, uint v) public returns (uint size) {
|
||||
// Actually calls itmap_impl.insert, auto-supplying the first parameter for us.
|
||||
IterableMapping.insert(data, k, v);
|
||||
// We can still access members of the struct - but we should take care not to mess with them.
|
||||
return data.size;
|
||||
}
|
||||
|
||||
// Computes the sum of all stored data.
|
||||
function sum() public view returns (uint s) {
|
||||
for (uint i = IterableMapping.iterate_start(data);
|
||||
IterableMapping.iterate_valid(data, i);
|
||||
i = IterableMapping.iterate_next(data, i)) {
|
||||
(, uint value) = IterableMapping.iterate_get(data, i);
|
||||
s += value;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ equivalent to ``a = 0``, but it can also be used on arrays, where it assigns a d
|
||||
array of length zero or a static array of the same length with all elements set to their
|
||||
initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and leaves
|
||||
all other elements and the length of the array untouched. This especially means that it leaves
|
||||
a gap in the array. If you plan to remove items, a mapping is probably a better choice.
|
||||
a gap in the array. If you plan to remove items, a :ref:`mapping <mapping-types>` is probably a better choice.
|
||||
|
||||
For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat:
|
||||
|
||||
|
@ -108,14 +108,16 @@ bool BMC::shouldInlineFunctionCall(FunctionCall const& _funCall)
|
||||
|
||||
bool BMC::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
SMTEncoder::visit(_contract);
|
||||
initContract(_contract);
|
||||
|
||||
/// Check targets created by state variable initialization.
|
||||
smt::Expression constraints = m_context.assertions();
|
||||
checkVerificationTargets(constraints);
|
||||
m_verificationTargets.clear();
|
||||
|
||||
return true;
|
||||
SMTEncoder::visit(_contract);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BMC::endVisit(ContractDefinition const& _contract)
|
||||
|
@ -62,8 +62,7 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
reset();
|
||||
|
||||
if (!SMTEncoder::visit(_contract))
|
||||
return false;
|
||||
initContract(_contract);
|
||||
|
||||
m_stateVariables = _contract.stateVariablesIncludingInherited();
|
||||
|
||||
@ -89,7 +88,6 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
// If the contract has a constructor it is handled as a function.
|
||||
// Otherwise we zero-initialize all state vars.
|
||||
// TODO take into account state vars init values.
|
||||
if (!_contract.constructor())
|
||||
{
|
||||
string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
@ -108,7 +106,8 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
connectBlocks(constructorPred, interface());
|
||||
}
|
||||
|
||||
return true;
|
||||
SMTEncoder::visit(_contract);
|
||||
return false;
|
||||
}
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
|
@ -50,8 +50,7 @@ void CVC4Interface::pop()
|
||||
|
||||
void CVC4Interface::declareVariable(string const& _name, Sort const& _sort)
|
||||
{
|
||||
if (!m_variables.count(_name))
|
||||
m_variables.insert({_name, m_context.mkVar(_name.c_str(), cvc4Sort(_sort))});
|
||||
m_variables[_name] = m_context.mkVar(_name.c_str(), cvc4Sort(_sort));
|
||||
}
|
||||
|
||||
void CVC4Interface::addAssertion(Expression const& _expr)
|
||||
|
@ -36,12 +36,37 @@ SMTEncoder::SMTEncoder(smt::EncodingContext& _context):
|
||||
|
||||
bool SMTEncoder::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_currentContract == nullptr, "");
|
||||
m_currentContract = &_contract;
|
||||
solAssert(m_currentContract, "");
|
||||
|
||||
initializeStateVariables(_contract);
|
||||
for (auto const& node: _contract.subNodes())
|
||||
if (!dynamic_pointer_cast<FunctionDefinition>(node))
|
||||
node->accept(*this);
|
||||
|
||||
return true;
|
||||
vector<FunctionDefinition const*> resolvedFunctions = _contract.definedFunctions();
|
||||
for (auto const& base: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto const& baseFunction: base->definedFunctions())
|
||||
{
|
||||
if (baseFunction->isConstructor())
|
||||
continue;
|
||||
bool overridden = false;
|
||||
for (auto const& function: resolvedFunctions)
|
||||
if (
|
||||
function->name() == baseFunction->name() &&
|
||||
FunctionType(*function).asCallableFunction(false)->
|
||||
hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false))
|
||||
)
|
||||
{
|
||||
overridden = true;
|
||||
break;
|
||||
}
|
||||
if (!overridden)
|
||||
resolvedFunctions.push_back(baseFunction);
|
||||
}
|
||||
|
||||
for (auto const& function: resolvedFunctions)
|
||||
function->accept(*this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMTEncoder::endVisit(ContractDefinition const& _contract)
|
||||
@ -512,6 +537,14 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
}
|
||||
}
|
||||
|
||||
void SMTEncoder::initContract(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_currentContract == nullptr, "");
|
||||
m_currentContract = &_contract;
|
||||
|
||||
initializeStateVariables(_contract);
|
||||
}
|
||||
|
||||
void SMTEncoder::initFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
solAssert(m_callStack.empty(), "");
|
||||
|
@ -108,6 +108,7 @@ protected:
|
||||
void compareOperation(BinaryOperation const& _op);
|
||||
void booleanOperation(BinaryOperation const& _op);
|
||||
|
||||
void initContract(ContractDefinition const& _contract);
|
||||
void initFunction(FunctionDefinition const& _function);
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void visitRequire(FunctionCall const& _funCall);
|
||||
|
@ -54,18 +54,20 @@ void Z3Interface::declareVariable(string const& _name, Sort const& _sort)
|
||||
{
|
||||
if (_sort.kind == Kind::Function)
|
||||
declareFunction(_name, _sort);
|
||||
else if (!m_constants.count(_name))
|
||||
m_constants.insert({_name, m_context.constant(_name.c_str(), z3Sort(_sort))});
|
||||
else if (m_constants.count(_name))
|
||||
m_constants.at(_name) = m_context.constant(_name.c_str(), z3Sort(_sort));
|
||||
else
|
||||
m_constants.emplace(_name, m_context.constant(_name.c_str(), z3Sort(_sort)));
|
||||
}
|
||||
|
||||
void Z3Interface::declareFunction(string const& _name, Sort const& _sort)
|
||||
{
|
||||
solAssert(_sort.kind == smt::Kind::Function, "");
|
||||
if (!m_functions.count(_name))
|
||||
{
|
||||
FunctionSort fSort = dynamic_cast<FunctionSort const&>(_sort);
|
||||
m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(fSort.domain), z3Sort(*fSort.codomain))});
|
||||
}
|
||||
FunctionSort fSort = dynamic_cast<FunctionSort const&>(_sort);
|
||||
if (m_functions.count(_name))
|
||||
m_functions.at(_name) = m_context.function(_name.c_str(), z3Sort(fSort.domain), z3Sort(*fSort.codomain));
|
||||
else
|
||||
m_functions.emplace(_name, m_context.function(_name.c_str(), z3Sort(fSort.domain), z3Sort(*fSort.codomain)));
|
||||
}
|
||||
|
||||
void Z3Interface::addAssertion(Expression const& _expr)
|
||||
|
@ -586,6 +586,14 @@ string const& CompilerStack::eWasm(string const& _contractName) const
|
||||
return contract(_contractName).eWasm;
|
||||
}
|
||||
|
||||
eth::LinkerObject const& CompilerStack::eWasmObject(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
|
||||
return contract(_contractName).eWasmObject;
|
||||
}
|
||||
|
||||
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
@ -1055,7 +1063,9 @@ void CompilerStack::generateEWasm(ContractDefinition const& _contract)
|
||||
//cout << yul::AsmPrinter{}(*ewasmStack.parserResult()->code) << endl;
|
||||
|
||||
// Turn into eWasm text representation.
|
||||
compiledContract.eWasm = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm).assembly;
|
||||
auto result = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm);
|
||||
compiledContract.eWasm = std::move(result.assembly);
|
||||
compiledContract.eWasmObject = std::move(*result.bytecode);
|
||||
}
|
||||
|
||||
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const
|
||||
|
@ -243,9 +243,12 @@ public:
|
||||
/// @returns the optimized IR representation of a contract.
|
||||
std::string const& yulIROptimized(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the eWasm (text) representation of a contract.
|
||||
/// @returns the eWasm text representation of a contract.
|
||||
std::string const& eWasm(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the eWasm representation of a contract.
|
||||
eth::LinkerObject const& eWasmObject(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the assembled object for a contract.
|
||||
eth::LinkerObject const& object(std::string const& _contractName) const;
|
||||
|
||||
@ -323,7 +326,8 @@ private:
|
||||
eth::LinkerObject runtimeObject; ///< Runtime object.
|
||||
std::string yulIR; ///< Experimental Yul IR code.
|
||||
std::string yulIROptimized; ///< Optimized experimental Yul IR code.
|
||||
std::string eWasm; ///< Experimental eWasm code (text representation).
|
||||
std::string eWasm; ///< Experimental eWasm text representation
|
||||
eth::LinkerObject eWasmObject; ///< Experimental eWasm code
|
||||
mutable std::unique_ptr<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
|
||||
mutable std::unique_ptr<Json::Value const> abi;
|
||||
mutable std::unique_ptr<Json::Value const> userDocumentation;
|
||||
@ -357,7 +361,7 @@ private:
|
||||
/// The IR is stored but otherwise unused.
|
||||
void generateIR(ContractDefinition const& _contract);
|
||||
|
||||
/// Generate eWasm text representation for a single contract.
|
||||
/// Generate eWasm representation for a single contract.
|
||||
void generateEWasm(ContractDefinition const& _contract);
|
||||
|
||||
/// Links all the known library addresses in the available objects. Any unknown
|
||||
|
@ -925,6 +925,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
// eWasm
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wast", wildcardMatchesExperimental))
|
||||
contractData["ewasm"]["wast"] = compilerStack.eWasm(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wasm", wildcardMatchesExperimental))
|
||||
contractData["ewasm"]["wasm"] = compilerStack.eWasmObject(contractName).toHex();
|
||||
|
||||
// EVM
|
||||
Json::Value evmData(Json::objectValue);
|
||||
|
@ -202,7 +202,10 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
|
||||
Dialect const& dialect = languageToDialect(m_language, EVMVersion{});
|
||||
|
||||
MachineAssemblyObject object;
|
||||
object.assembly = EWasmObjectCompiler::compile(*m_parserResult, dialect);
|
||||
auto result = EWasmObjectCompiler::compile(*m_parserResult, dialect);
|
||||
object.assembly = std::move(result.first);
|
||||
object.bytecode = make_shared<dev::eth::LinkerObject>();
|
||||
object.bytecode->bytecode = std::move(result.second);
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ add_library(yul
|
||||
backends/wasm/EWasmObjectCompiler.h
|
||||
backends/wasm/EWasmToText.cpp
|
||||
backends/wasm/EWasmToText.h
|
||||
backends/wasm/BinaryTransform.cpp
|
||||
backends/wasm/BinaryTransform.h
|
||||
backends/wasm/WasmDialect.cpp
|
||||
backends/wasm/WasmDialect.h
|
||||
backends/wasm/WordSizeTransform.cpp
|
||||
|
582
libyul/backends/wasm/BinaryTransform.cpp
Normal file
582
libyul/backends/wasm/BinaryTransform.cpp
Normal file
@ -0,0 +1,582 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* EWasm to binary encoder.
|
||||
*/
|
||||
|
||||
#include <libyul/backends/wasm/BinaryTransform.h>
|
||||
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace yul;
|
||||
using namespace dev;
|
||||
using namespace yul::wasm;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bytes toBytes(uint8_t _b)
|
||||
{
|
||||
return bytes(1, _b);
|
||||
}
|
||||
|
||||
enum class Section: uint8_t
|
||||
{
|
||||
CUSTOM = 0x00,
|
||||
TYPE = 0x01,
|
||||
IMPORT = 0x02,
|
||||
FUNCTION = 0x03,
|
||||
MEMORY = 0x05,
|
||||
GLOBAL = 0x06,
|
||||
EXPORT = 0x07,
|
||||
CODE = 0x0a
|
||||
};
|
||||
|
||||
bytes toBytes(Section _s)
|
||||
{
|
||||
return toBytes(uint8_t(_s));
|
||||
}
|
||||
|
||||
enum class ValueType: uint8_t
|
||||
{
|
||||
Void = 0x40,
|
||||
Function = 0x60,
|
||||
I64 = 0x7e,
|
||||
I32 = 0x7f
|
||||
};
|
||||
|
||||
bytes toBytes(ValueType _vt)
|
||||
{
|
||||
return toBytes(uint8_t(_vt));
|
||||
}
|
||||
|
||||
enum class Export: uint8_t
|
||||
{
|
||||
Function = 0x0,
|
||||
Memory = 0x2
|
||||
};
|
||||
|
||||
bytes toBytes(Export _export)
|
||||
{
|
||||
return toBytes(uint8_t(_export));
|
||||
}
|
||||
|
||||
enum class Opcode: uint8_t
|
||||
{
|
||||
Unreachable = 0x00,
|
||||
Nop = 0x01,
|
||||
Block = 0x02,
|
||||
Loop = 0x03,
|
||||
If = 0x04,
|
||||
Else = 0x05,
|
||||
Try = 0x06,
|
||||
Catch = 0x07,
|
||||
Throw = 0x08,
|
||||
Rethrow = 0x09,
|
||||
BrOnExn = 0x0a,
|
||||
End = 0x0b,
|
||||
Br = 0x0c,
|
||||
BrIf = 0x0d,
|
||||
BrTable = 0x0e,
|
||||
Return = 0x0f,
|
||||
Call = 0x10,
|
||||
CallIndirect = 0x11,
|
||||
ReturnCall = 0x12,
|
||||
ReturnCallIndirect = 0x13,
|
||||
Drop = 0x1a,
|
||||
Select = 0x1b,
|
||||
LocalGet = 0x20,
|
||||
LocalSet = 0x21,
|
||||
LocalTee = 0x22,
|
||||
GlobalGet = 0x23,
|
||||
GlobalSet = 0x24,
|
||||
I32Const = 0x41,
|
||||
I64Const = 0x42,
|
||||
};
|
||||
|
||||
bytes toBytes(Opcode _o)
|
||||
{
|
||||
return toBytes(uint8_t(_o));
|
||||
}
|
||||
|
||||
static std::map<string, uint8_t> const builtins = {
|
||||
{"i32.load", 0x28},
|
||||
{"i64.load", 0x29},
|
||||
{"i32.load8_s", 0x2c},
|
||||
{"i32.load8_u", 0x2d},
|
||||
{"i32.load16_s", 0x2e},
|
||||
{"i32.load16_u", 0x2f},
|
||||
{"i64.load8_s", 0x30},
|
||||
{"i64.load8_u", 0x31},
|
||||
{"i64.load16_s", 0x32},
|
||||
{"i64.load16_u", 0x33},
|
||||
{"i64.load32_s", 0x34},
|
||||
{"i64.load32_u", 0x35},
|
||||
{"i32.store", 0x36},
|
||||
{"i64.store", 0x37},
|
||||
{"i32.store8", 0x3a},
|
||||
{"i32.store16", 0x3b},
|
||||
{"i64.store8", 0x3c},
|
||||
{"i64.store16", 0x3d},
|
||||
{"i64.store32", 0x3e},
|
||||
{"memory.size", 0x3f},
|
||||
{"memory.grow", 0x40},
|
||||
{"i32.eqz", 0x45},
|
||||
{"i32.eq", 0x46},
|
||||
{"i32.ne", 0x47},
|
||||
{"i32.lt_s", 0x48},
|
||||
{"i32.lt_u", 0x49},
|
||||
{"i32.gt_s", 0x4a},
|
||||
{"i32.gt_u", 0x4b},
|
||||
{"i32.le_s", 0x4c},
|
||||
{"i32.le_u", 0x4d},
|
||||
{"i32.ge_s", 0x4e},
|
||||
{"i32.ge_u", 0x4f},
|
||||
{"i64.eqz", 0x50},
|
||||
{"i64.eq", 0x51},
|
||||
{"i64.ne", 0x52},
|
||||
{"i64.lt_s", 0x53},
|
||||
{"i64.lt_u", 0x54},
|
||||
{"i64.gt_s", 0x55},
|
||||
{"i64.gt_u", 0x56},
|
||||
{"i64.le_s", 0x57},
|
||||
{"i64.le_u", 0x58},
|
||||
{"i64.ge_s", 0x59},
|
||||
{"i64.ge_u", 0x5a},
|
||||
{"i32.clz", 0x67},
|
||||
{"i32.ctz", 0x68},
|
||||
{"i32.popcnt", 0x69},
|
||||
{"i32.add", 0x6a},
|
||||
{"i32.sub", 0x6b},
|
||||
{"i32.mul", 0x6c},
|
||||
{"i32.div_s", 0x6d},
|
||||
{"i32.div_u", 0x6e},
|
||||
{"i32.rem_s", 0x6f},
|
||||
{"i32.rem_u", 0x70},
|
||||
{"i32.and", 0x71},
|
||||
{"i32.or", 0x72},
|
||||
{"i32.xor", 0x73},
|
||||
{"i32.shl", 0x74},
|
||||
{"i32.shr_s", 0x75},
|
||||
{"i32.shr_u", 0x76},
|
||||
{"i32.rotl", 0x77},
|
||||
{"i32.rotr", 0x78},
|
||||
{"i64.clz", 0x79},
|
||||
{"i64.ctz", 0x7a},
|
||||
{"i64.popcnt", 0x7b},
|
||||
{"i64.add", 0x7c},
|
||||
{"i64.sub", 0x7d},
|
||||
{"i64.mul", 0x7e},
|
||||
{"i64.div_s", 0x7f},
|
||||
{"i64.div_u", 0x80},
|
||||
{"i64.rem_s", 0x81},
|
||||
{"i64.rem_u", 0x82},
|
||||
{"i64.and", 0x83},
|
||||
{"i64.or", 0x84},
|
||||
{"i64.xor", 0x85},
|
||||
{"i64.shl", 0x86},
|
||||
{"i64.shr_s", 0x87},
|
||||
{"i64.shr_u", 0x88},
|
||||
{"i64.rotl", 0x89},
|
||||
{"i64.rotr", 0x8a},
|
||||
{"i32.wrap_i64", 0xa7},
|
||||
{"i64.extend_i32_s", 0xac},
|
||||
{"i64.extend_i32_u", 0xad},
|
||||
};
|
||||
|
||||
bytes lebEncode(uint64_t _n)
|
||||
{
|
||||
bytes encoded;
|
||||
while (_n > 0x7f)
|
||||
{
|
||||
encoded.emplace_back(uint8_t(0x80 | (_n & 0x7f)));
|
||||
_n >>= 7;
|
||||
}
|
||||
encoded.emplace_back(_n);
|
||||
return encoded;
|
||||
}
|
||||
|
||||
bytes lebEncodeSigned(int64_t _n)
|
||||
{
|
||||
if (_n >= 0 && _n < 0x40)
|
||||
return toBytes(uint8_t(uint64_t(_n) & 0xff));
|
||||
else if (-_n > 0 && -_n < 0x40)
|
||||
return toBytes(uint8_t(uint64_t(_n + 0x80) & 0xff));
|
||||
else
|
||||
return toBytes(uint8_t(0x80 | uint8_t(_n & 0x7f))) + lebEncodeSigned(_n / 0x80);
|
||||
}
|
||||
|
||||
bytes prefixSize(bytes _data)
|
||||
{
|
||||
size_t size = _data.size();
|
||||
return lebEncode(size) + std::move(_data);
|
||||
}
|
||||
|
||||
bytes makeSection(Section _section, bytes _data)
|
||||
{
|
||||
return toBytes(_section) + prefixSize(std::move(_data));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bytes BinaryTransform::run(Module const& _module)
|
||||
{
|
||||
BinaryTransform bt;
|
||||
|
||||
for (size_t i = 0; i < _module.globals.size(); ++i)
|
||||
bt.m_globals[_module.globals[i].variableName] = i;
|
||||
|
||||
size_t funID = 0;
|
||||
for (FunctionImport const& fun: _module.imports)
|
||||
bt.m_functions[fun.internalName] = funID++;
|
||||
for (FunctionDefinition const& fun: _module.functions)
|
||||
bt.m_functions[fun.name] = funID++;
|
||||
|
||||
bytes ret{0, 'a', 's', 'm'};
|
||||
// version
|
||||
ret += bytes{1, 0, 0, 0};
|
||||
ret += bt.typeSection(_module.imports, _module.functions);
|
||||
ret += bt.importSection(_module.imports);
|
||||
ret += bt.functionSection(_module.functions);
|
||||
ret += bt.memorySection();
|
||||
ret += bt.globalSection();
|
||||
ret += bt.exportSection();
|
||||
for (auto const& sub: _module.subModules)
|
||||
{
|
||||
// TODO should we prefix and / or shorten the name?
|
||||
bytes data = BinaryTransform::run(sub.second);
|
||||
size_t length = data.size();
|
||||
ret += bt.customSection(sub.first, std::move(data));
|
||||
bt.m_subModulePosAndSize[sub.first] = {ret.size() - length, length};
|
||||
}
|
||||
ret += bt.codeSection(_module.functions);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(Literal const& _literal)
|
||||
{
|
||||
return toBytes(Opcode::I64Const) + lebEncodeSigned(_literal.value);
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(StringLiteral const&)
|
||||
{
|
||||
// TODO is this used?
|
||||
yulAssert(false, "String literals not yet implemented");
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(LocalVariable const& _variable)
|
||||
{
|
||||
return toBytes(Opcode::LocalGet) + lebEncode(m_locals.at(_variable.name));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(GlobalVariable const& _variable)
|
||||
{
|
||||
return toBytes(Opcode::GlobalGet) + lebEncode(m_globals.at(_variable.name));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(BuiltinCall const& _call)
|
||||
{
|
||||
// We need to avoid visiting the arguments of `dataoffset` and `datasize` because
|
||||
// they are references to object names that should not end up in the code.
|
||||
if (_call.functionName == "dataoffset")
|
||||
{
|
||||
string name = boost::get<StringLiteral>(_call.arguments.at(0)).value;
|
||||
return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).first);
|
||||
}
|
||||
else if (_call.functionName == "datasize")
|
||||
{
|
||||
string name = boost::get<StringLiteral>(_call.arguments.at(0)).value;
|
||||
return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).second);
|
||||
}
|
||||
|
||||
bytes args = visit(_call.arguments);
|
||||
|
||||
if (_call.functionName == "unreachable")
|
||||
return toBytes(Opcode::Unreachable);
|
||||
else
|
||||
{
|
||||
yulAssert(builtins.count(_call.functionName), "Builtin " + _call.functionName + " not found");
|
||||
bytes ret = std::move(args) + toBytes(builtins.at(_call.functionName));
|
||||
if (
|
||||
_call.functionName.find(".load") != string::npos ||
|
||||
_call.functionName.find(".store") != string::npos
|
||||
)
|
||||
// alignment and offset
|
||||
ret += bytes{{3, 0}};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(FunctionCall const& _call)
|
||||
{
|
||||
return visit(_call.arguments) + toBytes(Opcode::Call) + lebEncode(m_functions.at(_call.functionName));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(LocalAssignment const& _assignment)
|
||||
{
|
||||
return
|
||||
boost::apply_visitor(*this, *_assignment.value) +
|
||||
toBytes(Opcode::LocalSet) +
|
||||
lebEncode(m_locals.at(_assignment.variableName));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(GlobalAssignment const& _assignment)
|
||||
{
|
||||
return
|
||||
boost::apply_visitor(*this, *_assignment.value) +
|
||||
toBytes(Opcode::GlobalSet) +
|
||||
lebEncode(m_globals.at(_assignment.variableName));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(If const& _if)
|
||||
{
|
||||
bytes result =
|
||||
boost::apply_visitor(*this, *_if.condition) +
|
||||
toBytes(Opcode::If) +
|
||||
toBytes(ValueType::Void);
|
||||
|
||||
m_labels.push({});
|
||||
|
||||
result += visit(_if.statements);
|
||||
if (_if.elseStatements)
|
||||
result += toBytes(Opcode::Else) + visit(*_if.elseStatements);
|
||||
|
||||
m_labels.pop();
|
||||
|
||||
result += toBytes(Opcode::End);
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(Loop const& _loop)
|
||||
{
|
||||
bytes result = toBytes(Opcode::Loop) + toBytes(ValueType::Void);
|
||||
|
||||
m_labels.push(_loop.labelName);
|
||||
result += visit(_loop.statements);
|
||||
m_labels.pop();
|
||||
|
||||
result += toBytes(Opcode::End);
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(Break const&)
|
||||
{
|
||||
yulAssert(false, "br not yet implemented.");
|
||||
// TODO the index is just the nesting depth.
|
||||
return {};
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(BreakIf const&)
|
||||
{
|
||||
yulAssert(false, "br_if not yet implemented.");
|
||||
// TODO the index is just the nesting depth.
|
||||
return {};
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(Block const& _block)
|
||||
{
|
||||
return
|
||||
toBytes(Opcode::Block) +
|
||||
toBytes(ValueType::Void) +
|
||||
visit(_block.statements) +
|
||||
toBytes(Opcode::End);
|
||||
}
|
||||
|
||||
bytes BinaryTransform::operator()(FunctionDefinition const& _function)
|
||||
{
|
||||
bytes ret;
|
||||
|
||||
// This is a kind of run-length-encoding of local types. Has to be adapted once
|
||||
// we have locals of different types.
|
||||
ret += lebEncode(1); // number of locals groups
|
||||
ret += lebEncode(_function.locals.size());
|
||||
ret += toBytes(ValueType::I64);
|
||||
|
||||
m_locals.clear();
|
||||
size_t varIdx = 0;
|
||||
for (size_t i = 0; i < _function.parameterNames.size(); ++i)
|
||||
m_locals[_function.parameterNames[i]] = varIdx++;
|
||||
for (size_t i = 0; i < _function.locals.size(); ++i)
|
||||
m_locals[_function.locals[i].variableName] = varIdx++;
|
||||
|
||||
ret += visit(_function.body);
|
||||
ret += toBytes(Opcode::End);
|
||||
|
||||
return prefixSize(std::move(ret));
|
||||
}
|
||||
|
||||
BinaryTransform::Type BinaryTransform::typeOf(FunctionImport const& _import)
|
||||
{
|
||||
return {
|
||||
encodeTypes(_import.paramTypes),
|
||||
encodeTypes(_import.returnType ? vector<string>(1, *_import.returnType) : vector<string>())
|
||||
};
|
||||
}
|
||||
|
||||
BinaryTransform::Type BinaryTransform::typeOf(FunctionDefinition const& _funDef)
|
||||
{
|
||||
|
||||
return {
|
||||
encodeTypes(vector<string>(_funDef.parameterNames.size(), "i64")),
|
||||
encodeTypes(vector<string>(_funDef.returns ? 1 : 0, "i64"))
|
||||
};
|
||||
}
|
||||
|
||||
uint8_t BinaryTransform::encodeType(string const& _typeName)
|
||||
{
|
||||
if (_typeName == "i32")
|
||||
return uint8_t(ValueType::I32);
|
||||
else if (_typeName == "i64")
|
||||
return uint8_t(ValueType::I64);
|
||||
else
|
||||
yulAssert(false, "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
vector<uint8_t> BinaryTransform::encodeTypes(vector<string> const& _typeNames)
|
||||
{
|
||||
vector<uint8_t> result;
|
||||
for (auto const& t: _typeNames)
|
||||
result.emplace_back(encodeType(t));
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::typeSection(
|
||||
vector<FunctionImport> const& _imports,
|
||||
vector<FunctionDefinition> const& _functions
|
||||
)
|
||||
{
|
||||
map<Type, vector<string>> types;
|
||||
for (auto const& import: _imports)
|
||||
types[typeOf(import)].emplace_back(import.internalName);
|
||||
for (auto const& fun: _functions)
|
||||
types[typeOf(fun)].emplace_back(fun.name);
|
||||
|
||||
bytes result;
|
||||
size_t index = 0;
|
||||
for (auto const& [type, funNames]: types)
|
||||
{
|
||||
for (string const& name: funNames)
|
||||
m_functionTypes[name] = index;
|
||||
result += toBytes(ValueType::Function);
|
||||
result += lebEncode(type.first.size()) + type.first;
|
||||
result += lebEncode(type.second.size()) + type.second;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return makeSection(Section::TYPE, lebEncode(index) + std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::importSection(
|
||||
vector<FunctionImport> const& _imports
|
||||
)
|
||||
{
|
||||
bytes result = lebEncode(_imports.size());
|
||||
for (FunctionImport const& import: _imports)
|
||||
{
|
||||
uint8_t importKind = 0; // function
|
||||
result +=
|
||||
encodeName(import.module) +
|
||||
encodeName(import.externalName) +
|
||||
toBytes(importKind) +
|
||||
lebEncode(m_functionTypes[import.internalName]);
|
||||
}
|
||||
return makeSection(Section::IMPORT, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::functionSection(vector<FunctionDefinition> const& _functions)
|
||||
{
|
||||
bytes result = lebEncode(_functions.size());
|
||||
for (auto const& fun: _functions)
|
||||
result += lebEncode(m_functionTypes.at(fun.name));
|
||||
return makeSection(Section::FUNCTION, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::memorySection()
|
||||
{
|
||||
bytes result = lebEncode(1);
|
||||
result.push_back(0); // flags
|
||||
result.push_back(1); // initial
|
||||
return makeSection(Section::MEMORY, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::globalSection()
|
||||
{
|
||||
bytes result = lebEncode(m_globals.size());
|
||||
for (size_t i = 0; i < m_globals.size(); ++i)
|
||||
result +=
|
||||
// mutable i64
|
||||
bytes{uint8_t(ValueType::I64), 1} +
|
||||
toBytes(Opcode::I64Const) +
|
||||
lebEncodeSigned(0) +
|
||||
toBytes(Opcode::End);
|
||||
|
||||
return makeSection(Section::GLOBAL, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::exportSection()
|
||||
{
|
||||
bytes result = lebEncode(2);
|
||||
result += encodeName("memory") + toBytes(Export::Memory) + lebEncode(0);
|
||||
result += encodeName("main") + toBytes(Export::Function) + lebEncode(m_functions.at("main"));
|
||||
return makeSection(Section::EXPORT, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::customSection(string const& _name, bytes _data)
|
||||
{
|
||||
bytes result = encodeName(_name) + std::move(_data);
|
||||
return makeSection(Section::CUSTOM, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::codeSection(vector<wasm::FunctionDefinition> const& _functions)
|
||||
{
|
||||
bytes result = lebEncode(_functions.size());
|
||||
for (FunctionDefinition const& fun: _functions)
|
||||
result += (*this)(fun);
|
||||
return makeSection(Section::CODE, std::move(result));
|
||||
}
|
||||
|
||||
bytes BinaryTransform::visit(vector<Expression> const& _expressions)
|
||||
{
|
||||
bytes result;
|
||||
for (auto const& expr: _expressions)
|
||||
result += boost::apply_visitor(*this, expr);
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::visitReversed(vector<Expression> const& _expressions)
|
||||
{
|
||||
bytes result;
|
||||
for (auto const& expr: _expressions | boost::adaptors::reversed)
|
||||
result += boost::apply_visitor(*this, expr);
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes BinaryTransform::encodeName(std::string const& _name)
|
||||
{
|
||||
// UTF-8 is allowed here by the Wasm spec, but since all names here should stem from
|
||||
// Solidity or Yul identifiers or similar, non-ascii characters ending up here
|
||||
// is a very bad sign.
|
||||
for (char c: _name)
|
||||
yulAssert(uint8_t(c) <= 0x7f, "Non-ascii character found.");
|
||||
return lebEncode(_name.size()) + asBytes(_name);
|
||||
}
|
94
libyul/backends/wasm/BinaryTransform.h
Normal file
94
libyul/backends/wasm/BinaryTransform.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* EWasm to binary encoder.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/backends/wasm/EWasmAST.h>
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
namespace wasm
|
||||
{
|
||||
|
||||
/**
|
||||
* Web assembly to binary transform.
|
||||
*/
|
||||
class BinaryTransform: public boost::static_visitor<dev::bytes>
|
||||
{
|
||||
public:
|
||||
static dev::bytes run(Module const& _module);
|
||||
|
||||
dev::bytes operator()(wasm::Literal const& _literal);
|
||||
dev::bytes operator()(wasm::StringLiteral const& _literal);
|
||||
dev::bytes operator()(wasm::LocalVariable const& _identifier);
|
||||
dev::bytes operator()(wasm::GlobalVariable const& _identifier);
|
||||
dev::bytes operator()(wasm::BuiltinCall const& _builinCall);
|
||||
dev::bytes operator()(wasm::FunctionCall const& _functionCall);
|
||||
dev::bytes operator()(wasm::LocalAssignment const& _assignment);
|
||||
dev::bytes operator()(wasm::GlobalAssignment const& _assignment);
|
||||
dev::bytes operator()(wasm::If const& _if);
|
||||
dev::bytes operator()(wasm::Loop const& _loop);
|
||||
dev::bytes operator()(wasm::Break const& _break);
|
||||
dev::bytes operator()(wasm::BreakIf const& _break);
|
||||
dev::bytes operator()(wasm::Block const& _block);
|
||||
dev::bytes operator()(wasm::FunctionDefinition const& _function);
|
||||
|
||||
private:
|
||||
using Type = std::pair<std::vector<std::uint8_t>, std::vector<std::uint8_t>>;
|
||||
static Type typeOf(wasm::FunctionImport const& _import);
|
||||
static Type typeOf(wasm::FunctionDefinition const& _funDef);
|
||||
|
||||
static uint8_t encodeType(std::string const& _typeName);
|
||||
static std::vector<uint8_t> encodeTypes(std::vector<std::string> const& _typeNames);
|
||||
dev::bytes typeSection(
|
||||
std::vector<wasm::FunctionImport> const& _imports,
|
||||
std::vector<wasm::FunctionDefinition> const& _functions
|
||||
);
|
||||
|
||||
dev::bytes importSection(std::vector<wasm::FunctionImport> const& _imports);
|
||||
dev::bytes functionSection(std::vector<wasm::FunctionDefinition> const& _functions);
|
||||
dev::bytes memorySection();
|
||||
dev::bytes globalSection();
|
||||
dev::bytes exportSection();
|
||||
dev::bytes customSection(std::string const& _name, dev::bytes _data);
|
||||
dev::bytes codeSection(std::vector<wasm::FunctionDefinition> const& _functions);
|
||||
|
||||
dev::bytes visit(std::vector<wasm::Expression> const& _expressions);
|
||||
dev::bytes visitReversed(std::vector<wasm::Expression> const& _expressions);
|
||||
|
||||
static dev::bytes encodeName(std::string const& _name);
|
||||
|
||||
std::map<std::string, size_t> m_locals;
|
||||
std::map<std::string, size_t> m_globals;
|
||||
std::map<std::string, size_t> m_functions;
|
||||
std::map<std::string, size_t> m_functionTypes;
|
||||
std::stack<std::string> m_labels;
|
||||
std::map<std::string, std::pair<size_t, size_t>> m_subModulePosAndSize;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <boost/variant.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
@ -88,6 +90,16 @@ struct FunctionDefinition
|
||||
std::vector<Expression> body;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract representation of a wasm module.
|
||||
*/
|
||||
struct Module
|
||||
{
|
||||
std::vector<GlobalVariableDeclaration> globals;
|
||||
std::vector<FunctionImport> imports;
|
||||
std::vector<FunctionDefinition> functions;
|
||||
std::map<std::string, Module> subModules;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
#include <libyul/backends/wasm/EWasmCodeTransform.h>
|
||||
|
||||
#include <libyul/backends/wasm/EWasmToText.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
|
||||
#include <libyul/AsmData.h>
|
||||
@ -37,10 +36,11 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast)
|
||||
wasm::Module EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast)
|
||||
{
|
||||
wasm::Module module;
|
||||
|
||||
EWasmCodeTransform transform(_dialect, _ast);
|
||||
vector<wasm::FunctionDefinition> functions;
|
||||
|
||||
for (auto const& statement: _ast.statements)
|
||||
{
|
||||
@ -49,17 +49,14 @@ string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast)
|
||||
"Expected only function definitions at the highest level."
|
||||
);
|
||||
if (statement.type() == typeid(yul::FunctionDefinition))
|
||||
functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement)));
|
||||
module.functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement)));
|
||||
}
|
||||
|
||||
std::vector<wasm::FunctionImport> imports;
|
||||
for (auto& imp: transform.m_functionsToImport)
|
||||
imports.emplace_back(std::move(imp.second));
|
||||
return EWasmToText().run(
|
||||
transform.m_globalVariables,
|
||||
imports,
|
||||
functions
|
||||
);
|
||||
module.imports.emplace_back(std::move(imp.second));
|
||||
module.globals = transform.m_globalVariables;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
wasm::Expression EWasmCodeTransform::generateMultiAssignment(
|
||||
|
@ -35,7 +35,7 @@ struct AsmAnalysisInfo;
|
||||
class EWasmCodeTransform: public boost::static_visitor<wasm::Expression>
|
||||
{
|
||||
public:
|
||||
static std::string run(Dialect const& _dialect, yul::Block const& _ast);
|
||||
static wasm::Module run(Dialect const& _dialect, yul::Block const& _ast);
|
||||
|
||||
public:
|
||||
wasm::Expression operator()(yul::Literal const& _literal);
|
||||
|
@ -21,32 +21,36 @@
|
||||
#include <libyul/backends/wasm/EWasmObjectCompiler.h>
|
||||
|
||||
#include <libyul/backends/wasm/EWasmCodeTransform.h>
|
||||
#include <libyul/backends/wasm/BinaryTransform.h>
|
||||
#include <libyul/backends/wasm/EWasmToText.h>
|
||||
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
using namespace yul;
|
||||
using namespace std;
|
||||
|
||||
string EWasmObjectCompiler::compile(Object& _object, Dialect const& _dialect)
|
||||
pair<string, dev::bytes> EWasmObjectCompiler::compile(Object& _object, Dialect const& _dialect)
|
||||
{
|
||||
EWasmObjectCompiler compiler(_dialect);
|
||||
return compiler.run(_object);
|
||||
wasm::Module module = compiler.run(_object);
|
||||
return {EWasmToText().run(module), wasm::BinaryTransform::run(module)};
|
||||
}
|
||||
|
||||
string EWasmObjectCompiler::run(Object& _object)
|
||||
wasm::Module EWasmObjectCompiler::run(Object& _object)
|
||||
{
|
||||
string ret;
|
||||
yulAssert(_object.analysisInfo, "No analysis info.");
|
||||
yulAssert(_object.code, "No code.");
|
||||
|
||||
wasm::Module module = EWasmCodeTransform::run(m_dialect, *_object.code);
|
||||
|
||||
for (auto& subNode: _object.subObjects)
|
||||
if (Object* subObject = dynamic_cast<Object*>(subNode.get()))
|
||||
ret += compile(*subObject, m_dialect);
|
||||
module.subModules[subObject->name.str()] = run(*subObject);
|
||||
else
|
||||
yulAssert(false, "Data is not yet supported for EWasm.");
|
||||
|
||||
yulAssert(_object.analysisInfo, "No analysis info.");
|
||||
yulAssert(_object.code, "No code.");
|
||||
ret += EWasmCodeTransform::run(m_dialect, *_object.code);
|
||||
|
||||
return ret;
|
||||
return module;
|
||||
}
|
||||
|
@ -21,22 +21,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
using bytes = std::vector<uint8_t>;
|
||||
}
|
||||
namespace yul
|
||||
{
|
||||
struct Object;
|
||||
struct Dialect;
|
||||
namespace wasm
|
||||
{
|
||||
struct Module;
|
||||
}
|
||||
|
||||
class EWasmObjectCompiler
|
||||
{
|
||||
public:
|
||||
static std::string compile(Object& _object, Dialect const& _dialect);
|
||||
/// Compiles the given object and returns the WAST and the binary representation.
|
||||
static std::pair<std::string, dev::bytes> compile(Object& _object, Dialect const& _dialect);
|
||||
private:
|
||||
EWasmObjectCompiler(Dialect const& _dialect):
|
||||
m_dialect(_dialect)
|
||||
{}
|
||||
|
||||
std::string run(Object& _object);
|
||||
wasm::Module run(Object& _object);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
};
|
||||
|
@ -30,14 +30,15 @@ using namespace std;
|
||||
using namespace yul;
|
||||
using namespace dev;
|
||||
|
||||
string EWasmToText::run(
|
||||
vector<wasm::GlobalVariableDeclaration> const& _globals,
|
||||
vector<wasm::FunctionImport> const& _imports,
|
||||
vector<wasm::FunctionDefinition> const& _functions
|
||||
)
|
||||
string EWasmToText::run(wasm::Module const& _module)
|
||||
{
|
||||
string ret = "(module\n";
|
||||
for (wasm::FunctionImport const& imp: _imports)
|
||||
for (auto const& sub: _module.subModules)
|
||||
ret +=
|
||||
" ;; sub-module \"" +
|
||||
sub.first +
|
||||
"\" will be encoded as custom section in binary here, but is skipped in text mode.\n";
|
||||
for (wasm::FunctionImport const& imp: _module.imports)
|
||||
{
|
||||
ret += " (import \"" + imp.module + "\" \"" + imp.externalName + "\" (func $" + imp.internalName;
|
||||
if (!imp.paramTypes.empty())
|
||||
@ -52,10 +53,10 @@ string EWasmToText::run(
|
||||
// export the main function
|
||||
ret += " (export \"main\" (func $main))\n";
|
||||
|
||||
for (auto const& g: _globals)
|
||||
for (auto const& g: _module.globals)
|
||||
ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n";
|
||||
ret += "\n";
|
||||
for (auto const& f: _functions)
|
||||
for (auto const& f: _module.functions)
|
||||
ret += transform(f) + "\n";
|
||||
return move(ret) + ")\n";
|
||||
}
|
||||
|
@ -31,11 +31,7 @@ struct AsmAnalysisInfo;
|
||||
class EWasmToText: public boost::static_visitor<std::string>
|
||||
{
|
||||
public:
|
||||
std::string run(
|
||||
std::vector<wasm::GlobalVariableDeclaration> const& _globals,
|
||||
std::vector<wasm::FunctionImport> const& _imports,
|
||||
std::vector<wasm::FunctionDefinition> const& _functions
|
||||
);
|
||||
std::string run(wasm::Module const& _module);
|
||||
|
||||
public:
|
||||
std::string operator()(wasm::Literal const& _literal);
|
||||
|
@ -36,11 +36,6 @@ source "${REPO_ROOT}/scripts/common.sh"
|
||||
WORKDIR=`mktemp -d`
|
||||
CMDLINE_PID=
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]
|
||||
then
|
||||
SMT_FLAGS="--no-smt"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
# ensure failing commands don't cause termination during cleanup (especially within safe_kill)
|
||||
set +e
|
||||
|
@ -329,11 +329,18 @@ void CommandLineInterface::handleEWasm(string const& _contractName)
|
||||
if (m_args.count(g_argEWasm))
|
||||
{
|
||||
if (m_args.count(g_argOutputDir))
|
||||
{
|
||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->eWasm(_contractName));
|
||||
createFile(
|
||||
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
||||
asString(m_compiler->eWasmObject(_contractName).bytecode)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
sout() << "eWasm: " << endl;
|
||||
sout() << "EWasm text: " << endl;
|
||||
sout() << m_compiler->eWasm(_contractName) << endl;
|
||||
sout() << "EWasm binary (hex): " << m_compiler->eWasmObject(_contractName).toHex() << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,5 @@
|
||||
{"contracts":{"A":{"C":{"ewasm":{"wast":"(module
|
||||
(import \"ethereum\" \"revert\" (func $eth.revert (param i32 i32)))
|
||||
(memory $memory (export \"memory\") 1)
|
||||
(export \"main\" (func $main))
|
||||
|
||||
(func $main
|
||||
(local $_1 i64)
|
||||
(local $_2 i64)
|
||||
(local $_3 i64)
|
||||
(local $hi i64)
|
||||
(local $y i64)
|
||||
(local $hi_1 i64)
|
||||
(local.set $_1 (i64.const 0))
|
||||
(local.set $_2 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64)) (i64.const 64)))
|
||||
(local.set $_3 (i64.const 65280))
|
||||
(local.set $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (local.get $_1) (i64.const 8)) (local.get $_3)) (i64.and (i64.shr_u (local.get $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32)))
|
||||
(local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32)))))
|
||||
(i64.store (i32.wrap_i64 (local.get $_2)) (local.get $y))
|
||||
(i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 8))) (local.get $y))
|
||||
(i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 16))) (local.get $y))
|
||||
(local.set $hi_1 (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (i64.const 128) (i64.const 8)) (local.get $_3)) (i64.and (i64.shr_u (i64.const 128) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (i64.const 128) (i64.const 16)))) (i64.const 32)))
|
||||
(i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 24))) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32)))))
|
||||
(call $eth.revert (i32.wrap_i64 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))))
|
||||
)
|
||||
|
||||
(func $u256_to_i32
|
||||
(param $x1 i64)
|
||||
(param $x2 i64)
|
||||
(param $x3 i64)
|
||||
(param $x4 i64)
|
||||
(result i64)
|
||||
(local $v i64)
|
||||
(if (i64.ne (i64.extend_i32_u (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3)))) (i64.const 0)) (then
|
||||
(unreachable)))
|
||||
(if (i64.ne (i64.extend_i32_u (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32)))) (i64.const 0)) (then
|
||||
(unreachable)))
|
||||
(local.set $v (local.get $x4))
|
||||
(local.get $v)
|
||||
)
|
||||
|
||||
(func $endian_swap_16
|
||||
(param $x i64)
|
||||
(result i64)
|
||||
(local $y i64)
|
||||
(local.set $y (i64.or (i64.and (i64.shl (local.get $x) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (local.get $x) (i64.const 8)) (i64.const 255))))
|
||||
(local.get $y)
|
||||
)
|
||||
|
||||
(func $endian_swap_32
|
||||
(param $x i64)
|
||||
(result i64)
|
||||
(local $y i64)
|
||||
(local $hi i64)
|
||||
(local.set $hi (i64.shl (call $endian_swap_16 (local.get $x)) (i64.const 16)))
|
||||
(local.set $y (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $x) (i64.const 16)))))
|
||||
(local.get $y)
|
||||
)
|
||||
|
||||
)
|
||||
(module
|
||||
;; sub-module \"C_2_deployed\" will be encoded as custom section in binary here, but is skipped in text mode.
|
||||
(import \"ethereum\" \"codeCopy\" (func $eth.codeCopy (param i32 i32 i32)))
|
||||
(import \"ethereum\" \"finish\" (func $eth.finish (param i32 i32)))
|
||||
(memory $memory (export \"memory\") 1)
|
||||
|
25
test/libsolidity/smtCheckerTests/inheritance/functions_1.sol
Normal file
25
test/libsolidity/smtCheckerTests/inheritance/functions_1.sol
Normal file
@ -0,0 +1,25 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
// 2 warnings, A.f and A.g
|
||||
contract A {
|
||||
uint x;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
function g() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 2 warnings, B.f and A.g
|
||||
contract B is A {
|
||||
function f() public view {
|
||||
assert(x == 0);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning: (113-127): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
||||
// Warning: (259-273): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
27
test/libsolidity/smtCheckerTests/inheritance/functions_2.sol
Normal file
27
test/libsolidity/smtCheckerTests/inheritance/functions_2.sol
Normal file
@ -0,0 +1,27 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
// 2 warnings, A.f and A.g
|
||||
contract A {
|
||||
uint x;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
function g() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 2 warnings, B.f and A.g
|
||||
contract B is A {
|
||||
uint y;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 0);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning: (113-127): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
||||
// Warning: (269-283): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
47
test/libsolidity/smtCheckerTests/inheritance/functions_3.sol
Normal file
47
test/libsolidity/smtCheckerTests/inheritance/functions_3.sol
Normal file
@ -0,0 +1,47 @@
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
// 2 warnings, A.f and A.g
|
||||
contract A {
|
||||
uint x;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
function g() public view {
|
||||
assert(x == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 3 warnings, B.f, B.h, A.g
|
||||
contract B is A {
|
||||
uint y;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 0);
|
||||
}
|
||||
function h() public view {
|
||||
assert(x == 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 4 warnings, C.f, C.i, B.h, A.g
|
||||
contract C is B {
|
||||
uint z;
|
||||
|
||||
function f() public view {
|
||||
assert(x == 0);
|
||||
}
|
||||
function i() public view {
|
||||
assert(x == 0);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning: (113-127): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
||||
// Warning: (271-285): Assertion violation happens here
|
||||
// Warning: (320-334): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
||||
// Warning: (434-448): Assertion violation happens here
|
||||
// Warning: (483-497): Assertion violation happens here
|
||||
// Warning: (320-334): Assertion violation happens here
|
||||
// Warning: (162-176): Assertion violation happens here
|
Loading…
Reference in New Issue
Block a user