From c3705f268c589d651a17ea7dc79c56563402aaf7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 23 Apr 2019 10:45:16 +0200 Subject: [PATCH] Add global variables and support multi-return and multi-assignment. --- libyul/backends/wasm/EWasmAST.h | 16 +++- libyul/backends/wasm/EWasmCodeTransform.cpp | 90 ++++++++++++++------ libyul/backends/wasm/EWasmCodeTransform.h | 38 ++++++--- libyul/backends/wasm/EWasmObjectCompiler.cpp | 2 +- libyul/backends/wasm/EWasmToText.cpp | 19 ++++- libyul/backends/wasm/EWasmToText.h | 9 +- scripts/codespell_whitelist.txt | 1 + 7 files changed, 130 insertions(+), 45 deletions(-) diff --git a/libyul/backends/wasm/EWasmAST.h b/libyul/backends/wasm/EWasmAST.h index 16cc2024e..83817bbc6 100644 --- a/libyul/backends/wasm/EWasmAST.h +++ b/libyul/backends/wasm/EWasmAST.h @@ -30,24 +30,32 @@ namespace wasm { struct Literal; -struct Identifier; +struct LocalVariable; +struct GlobalVariable; struct Label; struct FunctionCall; struct BuiltinCall; struct LocalAssignment; +struct GlobalAssignment; struct Block; struct If; struct Loop; struct Break; struct Continue; -using Expression = boost::variant; +using Expression = boost::variant< + Literal, LocalVariable, GlobalVariable, Label, + FunctionCall, BuiltinCall, LocalAssignment, GlobalAssignment, + Block, If, Loop, Break, Continue +>; struct Literal { uint64_t value; }; -struct Identifier { std::string name; }; +struct LocalVariable { std::string name; }; +struct GlobalVariable { 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 GlobalAssignment { 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; }; @@ -55,6 +63,8 @@ struct Break { Label label; }; struct Continue { Label label; }; struct VariableDeclaration { std::string variableName; }; +struct GlobalVariableDeclaration { std::string variableName; }; + struct FunctionDefinition { std::string name; diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index 1ca102a3f..835395226 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -30,50 +31,68 @@ #include #include +#include using namespace std; using namespace dev; using namespace yul; -string EWasmCodeTransform::run(yul::Block const& _ast) +string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) { + EWasmCodeTransform transform(_dialect, _ast); vector functions; for (auto const& statement: _ast.statements) { yulAssert(statement.type() == typeid(yul::FunctionDefinition), ""); - functions.emplace_back(translateFunction(boost::get(statement))); + functions.emplace_back(transform.translateFunction(boost::get(statement))); } - return EWasmToText{}.run(functions); + return EWasmToText{}.run(transform.m_globalVariables, functions); +} + +wasm::Expression EWasmCodeTransform::generateMultiAssignment( + vector _variableNames, + unique_ptr _firstValue +) +{ + yulAssert(!_variableNames.empty(), ""); + wasm::LocalAssignment assignment{std::move(_variableNames.front()), std::move(_firstValue)}; + + if (_variableNames.size() == 1) + return move(assignment); + + wasm::Block block; + block.statements.emplace_back(std::move(assignment)); + for (size_t i = 1; i < _variableNames.size(); ++i) + block.statements.emplace_back(wasm::LocalAssignment{ + std::move(_variableNames.at(i)), + make_unique(wasm::GlobalVariable{m_globalVariables.at(i - 1).variableName}) + }); + return std::move(block); } wasm::Expression EWasmCodeTransform::operator()(VariableDeclaration const& _varDecl) { + vector variableNames; for (auto const& var: _varDecl.variables) - m_localVariables.emplace_back(wasm::VariableDeclaration{var.name.str()}); + { + variableNames.emplace_back(var.name.str()); + m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back()}); + } 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) - }; - } + return generateMultiAssignment(std::move(variableNames), 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) - }; + vector variableNames; + for (auto const& var: _assignment.variableNames) + variableNames.emplace_back(var.name.str()); + return generateMultiAssignment(std::move(variableNames), visit(*_assignment.value)); } wasm::Expression EWasmCodeTransform::operator()(StackAssignment const&) @@ -104,12 +123,16 @@ 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 + // If this function returns multiple values, then the first one will + // be returned in the expression itself and the others in global variables. + // The values have to be used right away in an assignment or variable declaration, + // so it is handled there. return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)}; } wasm::Expression EWasmCodeTransform::operator()(Identifier const& _identifier) { - return wasm::Identifier{_identifier.name.str()}; + return wasm::LocalVariable{_identifier.name.str()}; } wasm::Expression EWasmCodeTransform::operator()(Literal const& _literal) @@ -121,7 +144,7 @@ wasm::Expression EWasmCodeTransform::operator()(Literal const& _literal) wasm::Expression EWasmCodeTransform::operator()(yul::Instruction const&) { - yulAssert(false, ""); + yulAssert(false, "EVM instruction used for Wasm code generation."); return {}; } @@ -229,14 +252,31 @@ wasm::FunctionDefinition EWasmCodeTransform::translateFunction(yul::FunctionDefi 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()}); + + if (!_fun.returnVariables.empty()) + { + // First return variable is returned directly, the others are stored + // in globals. + allocateGlobals(_fun.returnVariables.size() - 1); + for (size_t i = 1; i < _fun.returnVariables.size(); ++i) + fun.body.emplace_back(wasm::GlobalAssignment{ + m_globalVariables.at(i - 1).variableName, + make_unique(wasm::LocalVariable{_fun.returnVariables.at(i).name.str()}) + }); + fun.body.emplace_back(wasm::LocalVariable{_fun.returnVariables.front().name.str()}); + } return fun; } string EWasmCodeTransform::newLabel() { - // TODO this should not clash with other identifiers! - return "label_" + to_string(++m_labelCounter); + return m_nameDispenser.newName("label_"_yulstring).str(); +} + +void EWasmCodeTransform::allocateGlobals(size_t _amount) +{ + while (m_globalVariables.size() < _amount) + m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{ + m_nameDispenser.newName("global_"_yulstring).str() + }); } diff --git a/libyul/backends/wasm/EWasmCodeTransform.h b/libyul/backends/wasm/EWasmCodeTransform.h index 9876dd54c..399428a31 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.h +++ b/libyul/backends/wasm/EWasmCodeTransform.h @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -33,14 +34,7 @@ struct AsmAnalysisInfo; class EWasmCodeTransform: public boost::static_visitor { public: - EWasmCodeTransform( - AsmAnalysisInfo&, - Dialect const& _dialect - ): - m_dialect(_dialect) - {} - - std::string run(yul::Block const& _ast); + static std::string run(Dialect const& _dialect, yul::Block const& _ast); public: wasm::Expression operator()(yul::Instruction const& _instruction); @@ -62,21 +56,41 @@ public: wasm::Expression operator()(yul::Block const& _block); private: + EWasmCodeTransform( + Dialect const& _dialect, + Block const& _ast + ): + m_dialect(_dialect), + m_nameDispenser(_dialect, _ast) + {} + 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); + /// Returns an assignment or a block containing multiple assignments. + /// @param _variableNames the names of the variables to assign to + /// @param _firstValue the value to be assigned to the first variable. If there + /// is more than one variable, the values are taken from m_globalVariables. + wasm::Expression generateMultiAssignment( + std::vector _variableNames, + std::unique_ptr _firstValue + ); + wasm::FunctionDefinition translateFunction(yul::FunctionDefinition const& _funDef); std::string newLabel(); - - std::vector m_localVariables; - size_t m_labelCounter = 0; - std::stack> m_breakContinueLabelNames; + /// Makes sure that there are at least @a _amount global variables. + void allocateGlobals(size_t _amount); Dialect const& m_dialect; + NameDispenser m_nameDispenser; + + std::vector m_localVariables; + std::vector m_globalVariables; + std::stack> m_breakContinueLabelNames; }; } diff --git a/libyul/backends/wasm/EWasmObjectCompiler.cpp b/libyul/backends/wasm/EWasmObjectCompiler.cpp index a2e2dad50..fb66c9d49 100644 --- a/libyul/backends/wasm/EWasmObjectCompiler.cpp +++ b/libyul/backends/wasm/EWasmObjectCompiler.cpp @@ -46,7 +46,7 @@ string EWasmObjectCompiler::run(Object& _object) yulAssert(_object.analysisInfo, "No analysis info."); yulAssert(_object.code, "No code."); - ret += EWasmCodeTransform{*_object.analysisInfo, m_dialect}.run(*_object.code); + ret += EWasmCodeTransform::run(m_dialect, *_object.code); return ret; } diff --git a/libyul/backends/wasm/EWasmToText.cpp b/libyul/backends/wasm/EWasmToText.cpp index f9f0eeede..633f90dcb 100644 --- a/libyul/backends/wasm/EWasmToText.cpp +++ b/libyul/backends/wasm/EWasmToText.cpp @@ -29,9 +29,14 @@ using namespace std; using namespace yul; -string EWasmToText::run(vector const& _functions) +string EWasmToText::run( + vector const& _globals, + vector const& _functions +) { string ret = "(module\n\n"; + for (auto const& g: _globals) + ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; for (auto const& f: _functions) ret += transform(f) + "\n"; return move(ret) + ")\n"; @@ -42,11 +47,16 @@ string EWasmToText::operator()(wasm::Literal const& _literal) return "(i64.const " + to_string(_literal.value) + ")"; } -string EWasmToText::operator()(wasm::Identifier const& _identifier) +string EWasmToText::operator()(wasm::LocalVariable const& _identifier) { return "(get_local $" + _identifier.name + ")"; } +string EWasmToText::operator()(wasm::GlobalVariable const& _identifier) +{ + return "(get_global $" + _identifier.name + ")"; +} + string EWasmToText::operator()(wasm::Label const& _label) { return "$" + _label.name; @@ -67,6 +77,11 @@ string EWasmToText::operator()(wasm::LocalAssignment const& _assignment) return "(set_local $" + _assignment.variableName + " " + visit(*_assignment.value) + ")\n"; } +string EWasmToText::operator()(wasm::GlobalAssignment const& _assignment) +{ + return "(set_global $" + _assignment.variableName + " " + visit(*_assignment.value) + ")\n"; +} + string EWasmToText::operator()(wasm::If const& _if) { return "(if " + visit(*_if.condition) + " (then\n" + indented(joinTransformed(_if.statements)) + "\n))\n"; diff --git a/libyul/backends/wasm/EWasmToText.h b/libyul/backends/wasm/EWasmToText.h index 1a752c483..61bcf5b32 100644 --- a/libyul/backends/wasm/EWasmToText.h +++ b/libyul/backends/wasm/EWasmToText.h @@ -31,15 +31,20 @@ struct AsmAnalysisInfo; class EWasmToText: public boost::static_visitor { public: - std::string run(std::vector const& _functions); + std::string run( + std::vector const& _globals, + std::vector const& _functions + ); public: std::string operator()(wasm::Literal const& _literal); - std::string operator()(wasm::Identifier const& _identifier); + std::string operator()(wasm::LocalVariable const& _identifier); + std::string operator()(wasm::GlobalVariable const& _identifier); std::string operator()(wasm::Label const& _label); std::string operator()(wasm::BuiltinCall const& _builinCall); std::string operator()(wasm::FunctionCall const& _functionCall); std::string operator()(wasm::LocalAssignment const& _assignment); + std::string operator()(wasm::GlobalAssignment const& _assignment); std::string operator()(wasm::If const& _if); std::string operator()(wasm::Loop const& _loop); std::string operator()(wasm::Break const& _break); diff --git a/scripts/codespell_whitelist.txt b/scripts/codespell_whitelist.txt index 0f5013cb1..b9467a805 100644 --- a/scripts/codespell_whitelist.txt +++ b/scripts/codespell_whitelist.txt @@ -2,3 +2,4 @@ iff nd assignend uint +mut