diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index c72f32d7b..ca569f9d1 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -200,7 +200,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const Dialect const& dialect = languageToDialect(m_language, EVMVersion{}); MachineAssemblyObject object; - object.assembly = EWasmObjectCompiler::compile(*m_parserResult, dialect); + object.assembly = EWasmObjectCompiler::compile(*m_parserResult, dialect).first; return object; } } diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 432ddeb14..f71171597 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -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 diff --git a/libyul/backends/wasm/BinaryTransform.cpp b/libyul/backends/wasm/BinaryTransform.cpp new file mode 100644 index 000000000..18f02ec02 --- /dev/null +++ b/libyul/backends/wasm/BinaryTransform.cpp @@ -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 . +*/ +/** + * EWasm to binary encoder. + */ + +#include + +#include +#include + +#include + +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 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(_call.arguments.at(0)).value; + return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).first); + } + else if (_call.functionName == "datasize") + { + string name = boost::get(_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(1, *_import.returnType) : vector()) + }; +} + +BinaryTransform::Type BinaryTransform::typeOf(FunctionDefinition const& _funDef) +{ + + return { + encodeTypes(vector(_funDef.parameterNames.size(), "i64")), + encodeTypes(vector(_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 BinaryTransform::encodeTypes(vector const& _typeNames) +{ + vector result; + for (auto const& t: _typeNames) + result.emplace_back(encodeType(t)); + return result; +} + +bytes BinaryTransform::typeSection( + vector const& _imports, + vector const& _functions +) +{ + map> 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 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 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 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 const& _expressions) +{ + bytes result; + for (auto const& expr: _expressions) + result += boost::apply_visitor(*this, expr); + return result; +} + +bytes BinaryTransform::visitReversed(vector 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); +} diff --git a/libyul/backends/wasm/BinaryTransform.h b/libyul/backends/wasm/BinaryTransform.h new file mode 100644 index 000000000..35c611b21 --- /dev/null +++ b/libyul/backends/wasm/BinaryTransform.h @@ -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 . +*/ +/** + * EWasm to binary encoder. + */ + +#pragma once + +#include + +#include + +#include +#include + +namespace yul +{ +namespace wasm +{ + +/** + * Web assembly to binary transform. + */ +class BinaryTransform: public boost::static_visitor +{ +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>; + 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 encodeTypes(std::vector const& _typeNames); + dev::bytes typeSection( + std::vector const& _imports, + std::vector const& _functions + ); + + dev::bytes importSection(std::vector const& _imports); + dev::bytes functionSection(std::vector 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 const& _functions); + + dev::bytes visit(std::vector const& _expressions); + dev::bytes visitReversed(std::vector const& _expressions); + + static dev::bytes encodeName(std::string const& _name); + + std::map m_locals; + std::map m_globals; + std::map m_functions; + std::map m_functionTypes; + std::stack m_labels; + std::map> m_subModulePosAndSize; +}; + + +} +} + diff --git a/libyul/backends/wasm/EWasmAST.h b/libyul/backends/wasm/EWasmAST.h index 24a678908..677668ade 100644 --- a/libyul/backends/wasm/EWasmAST.h +++ b/libyul/backends/wasm/EWasmAST.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace yul { @@ -86,6 +88,16 @@ struct FunctionDefinition std::vector body; }; +/** + * Abstract representation of a wasm module. + */ +struct Module +{ + std::vector globals; + std::vector imports; + std::vector functions; + std::map subModules; +}; } } diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index 20ca375a7..277df0df2 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -20,7 +20,6 @@ #include -#include #include #include @@ -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 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(statement))); + module.functions.emplace_back(transform.translateFunction(boost::get(statement))); } - std::vector 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( diff --git a/libyul/backends/wasm/EWasmCodeTransform.h b/libyul/backends/wasm/EWasmCodeTransform.h index 0addbba1c..cb9927d20 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.h +++ b/libyul/backends/wasm/EWasmCodeTransform.h @@ -35,7 +35,7 @@ struct AsmAnalysisInfo; class EWasmCodeTransform: public boost::static_visitor { 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::Instruction const& _instruction); diff --git a/libyul/backends/wasm/EWasmObjectCompiler.cpp b/libyul/backends/wasm/EWasmObjectCompiler.cpp index 2b0c4c02f..7ba72735a 100644 --- a/libyul/backends/wasm/EWasmObjectCompiler.cpp +++ b/libyul/backends/wasm/EWasmObjectCompiler.cpp @@ -21,32 +21,36 @@ #include #include +#include +#include #include #include +#include + using namespace yul; using namespace std; -string EWasmObjectCompiler::compile(Object& _object, Dialect const& _dialect) +pair 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(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; } diff --git a/libyul/backends/wasm/EWasmObjectCompiler.h b/libyul/backends/wasm/EWasmObjectCompiler.h index 67d4f4098..c99d258e1 100644 --- a/libyul/backends/wasm/EWasmObjectCompiler.h +++ b/libyul/backends/wasm/EWasmObjectCompiler.h @@ -21,22 +21,33 @@ #pragma once #include +#include +#include +namespace dev +{ +using bytes = std::vector; +} 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 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; }; diff --git a/libyul/backends/wasm/EWasmToText.cpp b/libyul/backends/wasm/EWasmToText.cpp index 9608b8d17..3971be1f6 100644 --- a/libyul/backends/wasm/EWasmToText.cpp +++ b/libyul/backends/wasm/EWasmToText.cpp @@ -30,14 +30,15 @@ using namespace std; using namespace yul; using namespace dev; -string EWasmToText::run( - vector const& _globals, - vector const& _imports, - vector 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"; } diff --git a/libyul/backends/wasm/EWasmToText.h b/libyul/backends/wasm/EWasmToText.h index 377561869..6163fd80b 100644 --- a/libyul/backends/wasm/EWasmToText.h +++ b/libyul/backends/wasm/EWasmToText.h @@ -31,11 +31,7 @@ struct AsmAnalysisInfo; class EWasmToText: public boost::static_visitor { public: - std::string run( - std::vector const& _globals, - std::vector const& _imports, - std::vector const& _functions - ); + std::string run(wasm::Module const& _module); public: std::string operator()(wasm::Literal const& _literal); diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index 85f577852..7f7787e96 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -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)