From ec27f484a1e9b7075c79d836333d70b112243f4b Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 18 Apr 2019 14:39:48 +0200 Subject: [PATCH] Prototype for Wasm code transform into in-memory representation. --- libyul/AssemblyStack.cpp | 13 +- libyul/AssemblyStack.h | 2 +- libyul/CMakeLists.txt | 4 + libyul/backends/wasm/EWasmAST.h | 69 ++++++ libyul/backends/wasm/EWasmCodeTransform.cpp | 242 +++++++++++++++++++ libyul/backends/wasm/EWasmCodeTransform.h | 82 +++++++ libyul/backends/wasm/EWasmObjectCompiler.cpp | 52 ++++ libyul/backends/wasm/EWasmObjectCompiler.h | 44 ++++ libyul/backends/wasm/WasmDialect.cpp | 6 +- libyul/backends/wasm/WasmDialect.h | 2 +- 10 files changed, 510 insertions(+), 6 deletions(-) create mode 100644 libyul/backends/wasm/EWasmAST.h create mode 100644 libyul/backends/wasm/EWasmCodeTransform.cpp create mode 100644 libyul/backends/wasm/EWasmCodeTransform.h create mode 100644 libyul/backends/wasm/EWasmObjectCompiler.cpp create mode 100644 libyul/backends/wasm/EWasmObjectCompiler.h diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index db669d148..62e283800 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include @@ -55,6 +57,8 @@ shared_ptr languageToDialect(AssemblyStack::Language _language, EVMVers return EVMDialect::strictAssemblyForEVMObjects(_version); case AssemblyStack::Language::Yul: return Dialect::yul(); + case AssemblyStack::Language::EWasm: + return make_shared(); } solAssert(false, ""); return Dialect::yul(); @@ -178,7 +182,14 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const return object; } case Machine::eWasm: - solUnimplemented("eWasm backend is not yet implemented."); + { + solAssert(m_language == Language::EWasm, ""); + shared_ptr dialect = languageToDialect(m_language, EVMVersion{}); + + MachineAssemblyObject object; + object.assembly = EWasmObjectCompiler::compile(*m_parserResult, *dialect); + return object; + } } // unreachable return MachineAssemblyObject(); diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index fb6686c39..40649fc56 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -57,7 +57,7 @@ struct MachineAssemblyObject class AssemblyStack { public: - enum class Language { Yul, Assembly, StrictAssembly }; + enum class Language { Yul, Assembly, StrictAssembly, EWasm }; enum class Machine { EVM, EVM15, eWasm }; AssemblyStack(): diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index b70d813b4..90534eac7 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -36,6 +36,10 @@ add_library(yul backends/evm/EVMDialect.h backends/evm/EVMObjectCompiler.cpp backends/evm/EVMObjectCompiler.h + backends/wasm/EWasmCodeTransform.cpp + backends/wasm/EWasmCodeTransform.h + backends/wasm/EWasmObjectCompiler.cpp + backends/wasm/EWasmObjectCompiler.h backends/evm/NoOutputAssembly.h backends/evm/NoOutputAssembly.cpp backends/wasm/WasmDialect.cpp diff --git a/libyul/backends/wasm/EWasmAST.h b/libyul/backends/wasm/EWasmAST.h new file mode 100644 index 000000000..16cc2024e --- /dev/null +++ b/libyul/backends/wasm/EWasmAST.h @@ -0,0 +1,69 @@ +/* + 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 . +*/ +/** + * Simplified in-memory representation of a Wasm AST. + */ + +#pragma once + +#include +#include +#include + +namespace yul +{ +namespace wasm +{ + +struct Literal; +struct Identifier; +struct Label; +struct FunctionCall; +struct BuiltinCall; +struct LocalAssignment; +struct Block; +struct If; +struct Loop; +struct Break; +struct Continue; +using Expression = boost::variant; + +struct Literal { uint64_t value; }; +struct Identifier { std::string name; }; +struct Label { std::string name; }; +struct FunctionCall { std::string functionName; std::vector arguments; }; +struct BuiltinCall { std::string functionName; std::vector arguments; }; +struct LocalAssignment { std::string variableName; std::unique_ptr value; }; +struct Block { std::string labelName; std::vector statements; }; +struct If { std::unique_ptr condition; std::vector statements; }; +struct Loop { std::string labelName; std::vector statements; }; +struct Break { Label label; }; +struct Continue { Label label; }; + +struct VariableDeclaration { std::string variableName; }; +struct FunctionDefinition +{ + std::string name; + std::vector parameterNames; + bool returns; + std::vector locals; + std::vector body; +}; + + +} +} diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp new file mode 100644 index 000000000..162bbb204 --- /dev/null +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -0,0 +1,242 @@ +/* + 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 . +*/ +/** +* Common code generator for translating Yul / inline assembly to EWasm. +*/ + +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +string EWasmCodeTransform::run(yul::Block const& _ast) +{ + vector functions; + + for (auto const& statement: _ast.statements) + { + yulAssert(statement.type() == typeid(yul::FunctionDefinition), ""); + functions.emplace_back(translateFunction(boost::get(statement))); + } + + // TODO translate to text representation + + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(VariableDeclaration const& _varDecl) +{ + for (auto const& var: _varDecl.variables) + m_localVariables.emplace_back(wasm::VariableDeclaration{var.name.str()}); + + if (_varDecl.value) + { + // TODO otherwise, we have to work with globals. + solUnimplementedAssert(_varDecl.variables.size() == 1, "Only single-variable assignments supported."); + return wasm::LocalAssignment{ + _varDecl.variables.front().name.str(), + visit(*_varDecl.value) + }; + } + else + // TODO this could be handled better. + return wasm::BuiltinCall{"nop", {}}; +} + +wasm::Expression EWasmCodeTransform::operator()(Assignment const& _assignment) +{ + solUnimplementedAssert(_assignment.variableNames.size() == 1, "Only single-variable assignments supported."); + return wasm::LocalAssignment{ + _assignment.variableNames.front().name.str(), + visit(*_assignment.value) + }; +} + +wasm::Expression EWasmCodeTransform::operator()(StackAssignment const&) +{ + yulAssert(false, ""); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(ExpressionStatement const& _statement) +{ + return visitReturnByValue(_statement.expression); +} + +wasm::Expression EWasmCodeTransform::operator()(Label const&) +{ + yulAssert(false, ""); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const&) +{ + yulAssert(false, ""); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call) +{ + if (m_dialect.builtin(_call.functionName.name)) + return wasm::BuiltinCall{_call.functionName.name.str(), visit(_call.arguments)}; + else + return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)}; +} + +wasm::Expression EWasmCodeTransform::operator()(Identifier const& _identifier) +{ + return wasm::Identifier{_identifier.name.str()}; +} + +wasm::Expression EWasmCodeTransform::operator()(Literal const& _literal) +{ + u256 value = valueOfLiteral(_literal); + yulAssert(value <= numeric_limits::max(), ""); + return wasm::Literal{uint64_t(value)}; +} + +wasm::Expression EWasmCodeTransform::operator()(yul::Instruction const&) +{ + yulAssert(false, ""); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(If const& _if) +{ + return wasm::If{visit(*_if.condition), visit(_if.body.statements)}; +} + +wasm::Expression EWasmCodeTransform::operator()(Switch const&) +{ + solUnimplementedAssert(false, ""); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(FunctionDefinition const&) +{ + yulAssert(false, "Should not have visited here."); + return {}; +} + +wasm::Expression EWasmCodeTransform::operator()(ForLoop const& _for) +{ + string breakLabel = newLabel(); + string continueLabel = newLabel(); + m_breakContinueLabelNames.push({breakLabel, continueLabel}); + + // The AST is constructed in this weird way because of some strange + // problem with move semantics. + wasm::BuiltinCall loopCondition{"i64.eqz", {}}; + loopCondition.arguments.emplace_back(visitReturnByValue(*_for.condition)); + + wasm::BuiltinCall conditionCheck{"br_if", {}}; + conditionCheck.arguments.emplace_back(wasm::Label{breakLabel}); + conditionCheck.arguments.emplace_back(move(loopCondition)); + + wasm::Loop loop; + loop.statements = visit(_for.pre.statements); + loop.statements.emplace_back(move(conditionCheck)); + loop.statements.emplace_back(wasm::Block{continueLabel, visit(_for.body.statements)}); + loop.statements += visit(_for.post.statements); + + wasm::Block breakBlock{breakLabel, {}}; + breakBlock.statements.emplace_back(move(loop)); + return move(breakBlock); +} + +wasm::Expression EWasmCodeTransform::operator()(Break const&) +{ + return wasm::Break{wasm::Label{m_breakContinueLabelNames.top().first}}; +} + +wasm::Expression EWasmCodeTransform::operator()(Continue const&) +{ + return wasm::Continue{wasm::Label{m_breakContinueLabelNames.top().second}}; +} + +wasm::Expression EWasmCodeTransform::operator()(Block const& _block) +{ + return wasm::Block{{}, visit(_block.statements)}; +} + +unique_ptr EWasmCodeTransform::visit(yul::Expression const& _expression) +{ + return make_unique(boost::apply_visitor(*this, _expression)); +} + +wasm::Expression EWasmCodeTransform::visitReturnByValue(yul::Expression const& _expression) +{ + return boost::apply_visitor(*this, _expression); +} + +vector EWasmCodeTransform::visit(vector const& _expressions) +{ + vector ret; + for (auto const& e: _expressions) + ret.emplace_back(visitReturnByValue(e)); + return ret; +} + +wasm::Expression EWasmCodeTransform::visit(yul::Statement const& _statement) +{ + return boost::apply_visitor(*this, _statement); +} + +vector EWasmCodeTransform::visit(vector const& _statements) +{ + vector ret; + for (auto const& s: _statements) + ret.emplace_back(visit(s)); + return ret; +} + +wasm::FunctionDefinition EWasmCodeTransform::translateFunction(yul::FunctionDefinition const& _fun) +{ + wasm::FunctionDefinition fun; + fun.name = _fun.name.str(); + for (auto const& param: _fun.parameters) + fun.parameterNames.emplace_back(param.name.str()); + for (auto const& retParam: _fun.returnVariables) + fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str()}); + fun.returns = !_fun.returnVariables.empty(); + + yulAssert(m_localVariables.empty(), ""); + fun.body = visit(_fun.body.statements); + fun.locals += m_localVariables; + + m_localVariables.clear(); + yulAssert(_fun.returnVariables.size() <= 1, ""); + if (_fun.returnVariables.size() == 1) + fun.body.emplace_back(wasm::Identifier{_fun.returnVariables.front().name.str()}); + return fun; +} + +string EWasmCodeTransform::newLabel() +{ + // TODO this should not clash with other identifiers! + return "label_" + to_string(++m_labelCounter); +} diff --git a/libyul/backends/wasm/EWasmCodeTransform.h b/libyul/backends/wasm/EWasmCodeTransform.h new file mode 100644 index 000000000..9876dd54c --- /dev/null +++ b/libyul/backends/wasm/EWasmCodeTransform.h @@ -0,0 +1,82 @@ +/* + 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 . +*/ +/** + * Common code generator for translating Yul / inline assembly to EWasm. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace yul +{ +struct AsmAnalysisInfo; + +class EWasmCodeTransform: public boost::static_visitor +{ +public: + EWasmCodeTransform( + AsmAnalysisInfo&, + Dialect const& _dialect + ): + m_dialect(_dialect) + {} + + std::string run(yul::Block const& _ast); + +public: + wasm::Expression operator()(yul::Instruction const& _instruction); + wasm::Expression operator()(yul::Literal const& _literal); + wasm::Expression operator()(yul::Identifier const& _identifier); + wasm::Expression operator()(yul::FunctionalInstruction const& _instr); + wasm::Expression operator()(yul::FunctionCall const&); + wasm::Expression operator()(yul::ExpressionStatement const& _statement); + wasm::Expression operator()(yul::Label const& _label); + wasm::Expression operator()(yul::StackAssignment const& _assignment); + wasm::Expression operator()(yul::Assignment const& _assignment); + wasm::Expression operator()(yul::VariableDeclaration const& _varDecl); + wasm::Expression operator()(yul::If const& _if); + wasm::Expression operator()(yul::Switch const& _switch); + wasm::Expression operator()(yul::FunctionDefinition const&); + wasm::Expression operator()(yul::ForLoop const&); + wasm::Expression operator()(yul::Break const&); + wasm::Expression operator()(yul::Continue const&); + wasm::Expression operator()(yul::Block const& _block); + +private: + std::unique_ptr visit(yul::Expression const& _expression); + wasm::Expression visitReturnByValue(yul::Expression const& _expression); + std::vector visit(std::vector const& _expressions); + wasm::Expression visit(yul::Statement const& _statement); + std::vector visit(std::vector const& _statements); + + wasm::FunctionDefinition translateFunction(yul::FunctionDefinition const& _funDef); + + std::string newLabel(); + + std::vector m_localVariables; + size_t m_labelCounter = 0; + std::stack> m_breakContinueLabelNames; + + Dialect const& m_dialect; +}; + +} diff --git a/libyul/backends/wasm/EWasmObjectCompiler.cpp b/libyul/backends/wasm/EWasmObjectCompiler.cpp new file mode 100644 index 000000000..a2e2dad50 --- /dev/null +++ b/libyul/backends/wasm/EWasmObjectCompiler.cpp @@ -0,0 +1,52 @@ +/* + 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 . +*/ +/** + * Compiler that transforms Yul Objects to EWasm text representation. + */ + +#include + +#include + +#include +#include + +using namespace yul; +using namespace std; + +string EWasmObjectCompiler::compile(Object& _object, Dialect& _dialect) +{ + EWasmObjectCompiler compiler(_dialect); + return compiler.run(_object); +} + +string EWasmObjectCompiler::run(Object& _object) +{ + string ret; + + for (auto& subNode: _object.subObjects) + if (Object* subObject = dynamic_cast(subNode.get())) + ret += compile(*subObject, m_dialect); + else + yulAssert(false, "Data is not yet supported for EWasm."); + + yulAssert(_object.analysisInfo, "No analysis info."); + yulAssert(_object.code, "No code."); + ret += EWasmCodeTransform{*_object.analysisInfo, m_dialect}.run(*_object.code); + + return ret; +} diff --git a/libyul/backends/wasm/EWasmObjectCompiler.h b/libyul/backends/wasm/EWasmObjectCompiler.h new file mode 100644 index 000000000..79ad94bdc --- /dev/null +++ b/libyul/backends/wasm/EWasmObjectCompiler.h @@ -0,0 +1,44 @@ +/* + 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 . +*/ +/** + * Compiler that transforms Yul Objects to EWasm text representation. + */ + +#pragma once + +#include + +namespace yul +{ +struct Object; +struct Dialect; + +class EWasmObjectCompiler +{ +public: + static std::string compile(Object& _object, Dialect& _dialect); +private: + EWasmObjectCompiler(Dialect& _dialect): + m_dialect(_dialect) + {} + + std::string run(Object& _object); + + Dialect& m_dialect; +}; + +} diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 5e03b037f..62195c96b 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -23,8 +23,8 @@ using namespace std; using namespace yul; -WasmDialect::WasmDialect(AsmFlavour _flavour): - Dialect{_flavour} +WasmDialect::WasmDialect(): + Dialect{AsmFlavour::Strict} { for (auto const& name: { "i64.add", @@ -65,7 +65,7 @@ BuiltinFunction const* WasmDialect::builtin(YulString _name) const void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) { - YulString name{std::move(_name)}; + YulString name{move(_name)}; BuiltinFunction& f = m_functions[name]; f.name = name; f.parameters.resize(_params); diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 71509ca08..119635289 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -42,7 +42,7 @@ struct Object; */ struct WasmDialect: public Dialect { - WasmDialect(AsmFlavour _flavour); + WasmDialect(); BuiltinFunction const* builtin(YulString _name) const override;