From 949b00ed591303c531ed8fa73087b710b7a554de Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 22 Feb 2016 02:13:41 +0100 Subject: [PATCH 1/3] Parsing for inline assembly. --- libsolidity/CMakeLists.txt | 1 + libsolidity/ast/AST.h | 25 +++ libsolidity/ast/ASTJsonConverter.cpp | 10 + libsolidity/ast/ASTJsonConverter.h | 2 + libsolidity/ast/ASTPrinter.cpp | 12 + libsolidity/ast/ASTPrinter.h | 2 + libsolidity/ast/ASTVisitor.h | 4 + libsolidity/ast/AST_accept.h | 12 + libsolidity/inlineasm/AsmData.h | 70 ++++++ libsolidity/inlineasm/AsmParser.cpp | 212 ++++++++++++++++++ libsolidity/inlineasm/AsmParser.h | 55 +++++ .../interface/SourceReferenceFormatter.cpp | 9 +- .../interface/SourceReferenceFormatter.h | 3 +- libsolidity/parsing/Parser.cpp | 20 ++ libsolidity/parsing/Parser.h | 1 + solc/CommandLineInterface.cpp | 48 +++- solc/CommandLineInterface.h | 4 + solc/jsonCompiler.cpp | 18 +- test/libsolidity/InlineAssembly.cpp | 138 ++++++++++++ 19 files changed, 630 insertions(+), 16 deletions(-) create mode 100644 libsolidity/inlineasm/AsmData.h create mode 100644 libsolidity/inlineasm/AsmParser.cpp create mode 100644 libsolidity/inlineasm/AsmParser.h create mode 100644 test/libsolidity/InlineAssembly.cpp diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index ab9ee1e2e..5f4a1a065 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -6,6 +6,7 @@ aux_source_directory(codegen SRC_LIST) aux_source_directory(formal SRC_LIST) aux_source_directory(interface SRC_LIST) aux_source_directory(parsing SRC_LIST) +aux_source_directory(inlineasm SRC_LIST) set(EXECUTABLE solidity) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index d32d76a4b..f53c78f2b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -854,6 +855,30 @@ public: virtual StatementAnnotation& annotation() const override; }; +// Forward-declaration to InlineAssembly.h +class AsmData; + +/** + * Inline assembly. + */ +class InlineAssembly: public Statement +{ +public: + InlineAssembly( + SourceLocation const& _location, + ASTPointer const& _docString, + std::shared_ptr const& _operations + ): + Statement(_location, _docString), m_operations(_operations) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + AsmData const& operations() const { return *m_operations; } + +private: + std::shared_ptr m_operations; +}; + /** * Brace-enclosed block containing zero or more statements. */ diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 163e22f45..89d0bf350 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -157,6 +157,12 @@ bool ASTJsonConverter::visit(Mapping const&) return true; } +bool ASTJsonConverter::visit(InlineAssembly const&) +{ + addJsonNode("InlineAssembly", {}, true); + return true; +} + bool ASTJsonConverter::visit(Block const&) { addJsonNode("Block", {}, true); @@ -355,6 +361,10 @@ void ASTJsonConverter::endVisit(Mapping const&) { } +void ASTJsonConverter::endVisit(InlineAssembly const&) +{ +} + void ASTJsonConverter::endVisit(Block const&) { goUp(); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index b7fc84e98..91ee72e1c 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -57,6 +57,7 @@ public: bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(Mapping const& _node) override; + bool visit(InlineAssembly const& _node) override; bool visit(Block const& _node) override; bool visit(IfStatement const& _node) override; bool visit(WhileStatement const& _node) override; @@ -90,6 +91,7 @@ public: void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; void endVisit(Mapping const&) override; + void endVisit(InlineAssembly const&) override; void endVisit(Block const&) override; void endVisit(IfStatement const&) override; void endVisit(WhileStatement const&) override; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 283bc8f90..9ed9c6d5b 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -171,6 +171,13 @@ bool ASTPrinter::visit(ArrayTypeName const& _node) return goDeeper(); } +bool ASTPrinter::visit(InlineAssembly const& _node) +{ + writeLine("InlineAssembly"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Block const& _node) { writeLine("Block"); @@ -433,6 +440,11 @@ void ASTPrinter::endVisit(ArrayTypeName const&) m_indentation--; } +void ASTPrinter::endVisit(InlineAssembly const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Block const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index 334fefc77..a2546935f 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -64,6 +64,7 @@ public: bool visit(UserDefinedTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; + bool visit(InlineAssembly const& _node) override; bool visit(Block const& _node) override; bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; @@ -105,6 +106,7 @@ public: void endVisit(UserDefinedTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; + void endVisit(InlineAssembly const&) override; void endVisit(Block const&) override; void endVisit(PlaceholderStatement const&) override; void endVisit(IfStatement const&) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 625f395db..5aac20665 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -62,6 +62,7 @@ public: virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } virtual bool visit(Mapping& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } + virtual bool visit(InlineAssembly& _node) { return visitNode(_node); } virtual bool visit(Block& _node) { return visitNode(_node); } virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); } virtual bool visit(IfStatement& _node) { return visitNode(_node); } @@ -105,6 +106,7 @@ public: virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } virtual void endVisit(Mapping& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } + virtual void endVisit(InlineAssembly& _node) { endVisitNode(_node); } virtual void endVisit(Block& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); } virtual void endVisit(IfStatement& _node) { endVisitNode(_node); } @@ -166,6 +168,7 @@ public: virtual bool visit(WhileStatement const& _node) { return visitNode(_node); } virtual bool visit(ForStatement const& _node) { return visitNode(_node); } virtual bool visit(Continue const& _node) { return visitNode(_node); } + virtual bool visit(InlineAssembly const& _node) { return visitNode(_node); } virtual bool visit(Break const& _node) { return visitNode(_node); } virtual bool visit(Return const& _node) { return visitNode(_node); } virtual bool visit(Throw const& _node) { return visitNode(_node); } @@ -209,6 +212,7 @@ public: virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); } virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); } virtual void endVisit(Continue const& _node) { endVisitNode(_node); } + virtual void endVisit(InlineAssembly const& _node) { endVisitNode(_node); } virtual void endVisit(Break const& _node) { endVisitNode(_node); } virtual void endVisit(Return const& _node) { endVisitNode(_node); } virtual void endVisit(Throw const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index dee9d5b14..dd2a7d602 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -357,6 +357,18 @@ void ArrayTypeName::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void InlineAssembly::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void InlineAssembly::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void Block::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h new file mode 100644 index 000000000..a38a9d361 --- /dev/null +++ b/libsolidity/inlineasm/AsmData.h @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Parsed inline assembly to be used by the AST + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class AsmData +{ +public: + /// Direct EVM instruction (except PUSHi and JUMPDEST) + struct Instruction { eth::Instruction instruction; }; + /// Literal number or string (up to 32 bytes) + struct Literal { bool isNumber; std::string value; }; + /// External / internal identifier or label reference + struct Identifier { std::string name; }; + struct FunctionalInstruction; + /// Jump label ("name:") + struct Label { std::string name; }; + /// Assignemnt (":= x", moves stack top into x, potentially multiple slots) + struct Assignment { Identifier variableName; }; + struct FunctionalAssignment; + struct VariableDeclaration; + struct Block; + using Statement = boost::variant; + /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand + /// side and requires x to occupy exactly one stack slot. + struct FunctionalAssignment { Identifier variableName; std::shared_ptr value; }; + /// Functional instruction, e.g. "mul(mload(20), add(2, x))" + struct FunctionalInstruction { Instruction instruction; std::vector arguments; }; + /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted + struct VariableDeclaration { std::string name; std::shared_ptr value; }; + /// Block that creates a scope (frees declared stack variables) + struct Block { std::vector statements; }; + + AsmData(Block&& _statements): m_statements(_statements) {} + + Block const& statements() const { return m_statements; } + +private: + Block m_statements; +}; + +} +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp new file mode 100644 index 000000000..28fd53547 --- /dev/null +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -0,0 +1,212 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Solidity inline assembly parser. + */ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +shared_ptr InlineAssemblyParser::parse(std::shared_ptr const& _scanner) +{ + try + { + m_scanner = _scanner; + return make_shared(parseBlock()); + } + catch (FatalError const&) + { + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + } + return nullptr; +} + +AsmData::Block InlineAssemblyParser::parseBlock() +{ + expectToken(Token::LBrace); + AsmData::Block block; + while (m_scanner->currentToken() != Token::RBrace) + block.statements.emplace_back(parseStatement()); + m_scanner->next(); + return block; +} + +AsmData::Statement InlineAssemblyParser::parseStatement() +{ + switch (m_scanner->currentToken()) + { + case Token::Let: + return parseVariableDeclaration(); + case Token::LBrace: + return parseBlock(); + case Token::Assign: + { + m_scanner->next(); + expectToken(Token::Colon); + string name = m_scanner->currentLiteral(); + expectToken(Token::Identifier); + return AsmData::Assignment{AsmData::Identifier{name}}; + } + default: + break; + } + // Options left: + // Simple instruction (might turn into functional), + // literal, + // identifier (might turn into label or functional assignment) + AsmData::Statement statement(parseElementaryOperation()); + switch (m_scanner->currentToken()) + { + case Token::LParen: + return parseFunctionalInstruction(statement); + case Token::Colon: + { + if (statement.type() != typeid(AsmData::Identifier)) + fatalParserError("Label name / variable name must precede \":\"."); + string const& name = boost::get(statement).name; + m_scanner->next(); + if (m_scanner->currentToken() == Token::Assign) + { + // functional assignment + m_scanner->next(); + unique_ptr value; + value.reset(new AsmData::Statement(parseExpression())); + return AsmData::FunctionalAssignment{{move(name)}, move(value)}; + } + else + // label + return AsmData::Label{name}; + } + default: + break; + } + return statement; +} + +AsmData::Statement InlineAssemblyParser::parseExpression() +{ + AsmData::Statement operation = parseElementaryOperation(true); + if (m_scanner->currentToken() == Token::LParen) + return parseFunctionalInstruction(operation); + else + return operation; +} + +AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySinglePusher) +{ + // Allowed instructions, lowercase names. + static map s_instructions; + if (s_instructions.empty()) + for (auto const& instruction: eth::c_instructions) + { + if ( + instruction.second == eth::Instruction::JUMPDEST || + (eth::Instruction::PUSH1 <= instruction.second && instruction.second <= eth::Instruction::PUSH32) + ) + continue; + string name = instruction.first; + transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + s_instructions[name] = instruction.second; + } + + //@TODO track location + + switch (m_scanner->currentToken()) + { + case Token::Identifier: + { + string literal = m_scanner->currentLiteral(); + // first search the set of instructions. + if (s_instructions.count(literal)) + { + eth::Instruction const& instr = s_instructions[literal]; + if (_onlySinglePusher) + { + eth::InstructionInfo info = eth::instructionInfo(instr); + if (info.ret != 1) + fatalParserError("Instruction " + info.name + " not allowed in this context."); + } + m_scanner->next(); + return AsmData::Instruction{instr}; + } + else + m_scanner->next(); + return AsmData::Identifier{literal}; + break; + } + case Token::StringLiteral: + case Token::Number: + { + AsmData::Literal literal{ + m_scanner->currentToken() == Token::Number, + m_scanner->currentLiteral() + }; + m_scanner->next(); + return literal; + } + default: + break; + } + fatalParserError("Expected elementary inline assembly operation."); + return {}; +} + +AsmData::VariableDeclaration InlineAssemblyParser::parseVariableDeclaration() +{ + expectToken(Token::Let); + string name = m_scanner->currentLiteral(); + expectToken(Token::Identifier); + expectToken(Token::Colon); + expectToken(Token::Assign); + unique_ptr value; + value.reset(new AsmData::Statement(parseExpression())); + return AsmData::VariableDeclaration{name, move(value)}; +} + +AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction(AsmData::Statement const& _instruction) +{ + if (_instruction.type() != typeid(AsmData::Instruction)) + fatalParserError("Assembly instruction required in front of \"(\")"); + eth::Instruction instr = boost::get(_instruction).instruction; + eth::InstructionInfo instrInfo = eth::instructionInfo(instr); + if (eth::Instruction::DUP1 <= instr && instr <= eth::Instruction::DUP16) + fatalParserError("DUPi instructions not allowed for functional notation"); + if (eth::Instruction::SWAP1 <= instr && instr <= eth::Instruction::SWAP16) + fatalParserError("SWAPi instructions not allowed for functional notation"); + + expectToken(Token::LParen); + vector arguments; + unsigned args = unsigned(instrInfo.args); + for (unsigned i = 0; i < args; ++i) + { + arguments.push_back(parseExpression()); + if (i != args - 1) + expectToken(Token::Comma); + } + expectToken(Token::RParen); + return AsmData::FunctionalInstruction{{instr}, move(arguments)}; +} diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h new file mode 100644 index 000000000..fe84470d3 --- /dev/null +++ b/libsolidity/inlineasm/AsmParser.h @@ -0,0 +1,55 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Solidity inline assembly parser. + */ + +#pragma once + +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class InlineAssemblyParser: public ParserBase +{ +public: + InlineAssemblyParser(ErrorList& _errors): ParserBase(_errors) {} + + /// Parses an inline assembly block starting with `{` and ending with `}`. + /// @returns an empty shared pointer on error. + std::shared_ptr parse(std::shared_ptr const& _scanner); + +protected: + AsmData::Block parseBlock(); + AsmData::Statement parseStatement(); + /// Parses a functional expression that has to push exactly one stack element + AsmData::Statement parseExpression(); + AsmData::Statement parseElementaryOperation(bool _onlySinglePusher = false); + AsmData::VariableDeclaration parseVariableDeclaration(); + AsmData::FunctionalInstruction parseFunctionalInstruction(AsmData::Statement const& _instruction); +}; + +} +} diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 169e5c189..65d7fbc8b 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -21,7 +21,6 @@ */ #include -#include #include #include @@ -85,7 +84,7 @@ void SourceReferenceFormatter::printExceptionInformation( ostream& _stream, Exception const& _exception, string const& _name, - CompilerStack const& _compiler + function const& _scannerFromSourceName ) { SourceLocation const* location = boost::get_error_info(_exception); @@ -94,7 +93,7 @@ void SourceReferenceFormatter::printExceptionInformation( if (location) { - scannerPtr = &_compiler.scanner(*location->sourceName); + scannerPtr = &_scannerFromSourceName(*location->sourceName); printSourceName(_stream, *location, *scannerPtr); } @@ -104,7 +103,7 @@ void SourceReferenceFormatter::printExceptionInformation( if (location) { - scannerPtr = &_compiler.scanner(*location->sourceName); + scannerPtr = &_scannerFromSourceName(*location->sourceName); printSourceLocation(_stream, *location, *scannerPtr); } @@ -112,7 +111,7 @@ void SourceReferenceFormatter::printExceptionInformation( { for (auto info: secondarylocation->infos) { - scannerPtr = &_compiler.scanner(*info.second.sourceName); + scannerPtr = &_scannerFromSourceName(*info.second.sourceName); _stream << info.first << " "; printSourceName(_stream, info.second, *scannerPtr); _stream << endl; diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/libsolidity/interface/SourceReferenceFormatter.h index dd258c276..1fc42753e 100644 --- a/libsolidity/interface/SourceReferenceFormatter.h +++ b/libsolidity/interface/SourceReferenceFormatter.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include namespace dev @@ -44,7 +45,7 @@ public: std::ostream& _stream, Exception const& _exception, std::string const& _name, - CompilerStack const& _compiler + std::function const& _scannerFromSourceName ); private: static void printSourceName(std::ostream& _stream, SourceLocation const& _location, Scanner const& _scanner); diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 7bda36102..29377380c 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -20,11 +20,13 @@ * Solidity parser. */ +#include #include #include #include #include #include +#include #include #include @@ -712,6 +714,8 @@ ASTPointer Parser::parseStatement() m_scanner->next(); break; } + case Token::Assembly: + return parseInlineAssembly(docString); case Token::Identifier: if (m_insideModifier && m_scanner->currentLiteral() == "_") { @@ -727,6 +731,22 @@ ASTPointer Parser::parseStatement() return statement; } +ASTPointer Parser::parseInlineAssembly(ASTPointer const& _docString) +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Assembly); + if (m_scanner->currentToken() != Token::StringLiteral) + fatalParserError("Expected assembly name."); + if (m_scanner->currentLiteral() != "evmasm") + fatalParserError("Only \"evmasm\" supported."); + m_scanner->next(); + + InlineAssemblyParser parser(m_errors); + shared_ptr operations = parser.parse(m_scanner); + nodeFactory.markEndPosition(); + return nodeFactory.createNode(_docString, operations); +} + ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) { ASTNodeFactory nodeFactory(*this); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index a093cc5bf..d776c3fd3 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -81,6 +81,7 @@ private: ); ASTPointer parseBlock(ASTPointer const& _docString = {}); ASTPointer parseStatement(); + ASTPointer parseInlineAssembly(ASTPointer const& _docString = {}); ASTPointer parseIfStatement(ASTPointer const& _docString); ASTPointer parseWhileStatement(ASTPointer const& _docString); ASTPointer parseForStatement(ASTPointer const& _docString); diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 499b1f7aa..1cf87bc9d 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include using namespace std; @@ -430,6 +431,10 @@ Allowed options)", "Output a single json document containing the specified information." ) (g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.") + ( + "assemble", + "Switch to assembly mode, ignoring all options and assumes input is assembly." + ) ( "link", "Switch to linker mode, ignoring all options apart from --libraries " @@ -509,6 +514,12 @@ bool CommandLineInterface::processInput() if (!parseLibraryOption(library)) return false; + if (m_args.count("assemble")) + { + // switch to assembly mode + m_onlyAssemble = true; + return assemble(); + } if (m_args.count("link")) { // switch to linker mode @@ -574,6 +585,7 @@ bool CommandLineInterface::processInput() }; m_compiler.reset(new CompilerStack(m_args.count(g_argAddStandard) > 0, fileReader)); + auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); }; try { for (auto const& sourceCode: m_sourceCodes) @@ -593,7 +605,8 @@ bool CommandLineInterface::processInput() SourceReferenceFormatter::printExceptionInformation( cerr, *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error", *m_compiler + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + scannerFromSourceName ); if (!successful) @@ -601,7 +614,7 @@ bool CommandLineInterface::processInput() } catch (CompilerError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", *m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", scannerFromSourceName); return false; } catch (InternalCompilerError const& _exception) @@ -615,7 +628,7 @@ bool CommandLineInterface::processInput() if (_error.type() == Error::Type::DocstringParsingError) cerr << "Documentation parsing error: " << *boost::get_error_info(_error) << endl; else - SourceReferenceFormatter::printExceptionInformation(cerr, _error, _error.typeName(), *m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _error, _error.typeName(), scannerFromSourceName); return false; } @@ -759,7 +772,9 @@ void CommandLineInterface::handleAst(string const& _argStr) void CommandLineInterface::actOnInput() { - if (m_onlyLink) + if (m_onlyAssemble) + return; //@TODO + else if (m_onlyLink) writeLinkedFiles(); else outputCompilationResults(); @@ -812,6 +827,31 @@ void CommandLineInterface::writeLinkedFiles() writeFile(src.first, src.second); } +bool CommandLineInterface::assemble() +{ + //@TODO later, we will use the convenience interface and should also remove the include above + bool successful = true; + ErrorList errors; + map> scanners; + for (auto const& src: m_sourceCodes) + { + auto scanner = make_shared(CharStream(src.second), src.first); + scanners[src.first] = scanner; + InlineAssemblyParser parser(errors); + if (!parser.parse(scanner)) + successful = false; + } + for (auto const& error: errors) + SourceReferenceFormatter::printExceptionInformation( + cerr, + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } + ); + + return successful; +} + void CommandLineInterface::outputCompilationResults() { handleCombinedJSON(); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index d288b5c16..1c40e26d0 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -50,6 +50,9 @@ private: bool link(); void writeLinkedFiles(); + /// Parse assembly input. + bool assemble(); + void outputCompilationResults(); void handleCombinedJSON(); @@ -73,6 +76,7 @@ private: /// @arg _data to be written void createFile(std::string const& _fileName, std::string const& _data); + bool m_onlyAssemble = false; bool m_onlyLink = false; /// Compiler arguments variable map diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index b5efa94d4..c6b40fdcf 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -47,10 +48,14 @@ extern "C" { typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); } -string formatError(Exception const& _exception, string const& _name, CompilerStack const& _compiler) +string formatError( + Exception const& _exception, + string const& _name, + function const& _scannerFromSourceName +) { ostringstream errorOutput; - SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _compiler); + SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName); return errorOutput.str(); } @@ -150,6 +155,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback }; } CompilerStack compiler(true, readCallback); + auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); }; bool success = false; try { @@ -161,22 +167,22 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback errors.append(formatError( *error, (err->type() == Error::Type::Warning) ? "Warning" : "Error", - compiler + scannerFromSourceName )); } success = succ; // keep success false on exception } catch (Error const& error) { - errors.append(formatError(error, error.typeName(), compiler)); + errors.append(formatError(error, error.typeName(), scannerFromSourceName)); } catch (CompilerError const& exception) { - errors.append(formatError(exception, "Compiler error", compiler)); + errors.append(formatError(exception, "Compiler error", scannerFromSourceName)); } catch (InternalCompilerError const& exception) { - errors.append(formatError(exception, "Internal compiler error", compiler)); + errors.append(formatError(exception, "Internal compiler error", scannerFromSourceName)); } catch (Exception const& exception) { diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp new file mode 100644 index 000000000..c435f5d6d --- /dev/null +++ b/test/libsolidity/InlineAssembly.cpp @@ -0,0 +1,138 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Unit tests for inline assembly. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ +shared_ptr parse(std::string const& _source, ErrorList& _errors) +{ + return InlineAssemblyParser(_errors).parse(std::make_shared(CharStream(_source))); +} + +bool successParse(std::string const& _source) +{ + ErrorList errors; + try + { + auto assembly = parse(_source, errors); + if (!assembly) + return false; + } + catch (FatalError const&) + { + if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + return false; + } + if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + return false; + + BOOST_CHECK(Error::containsOnlyWarnings(errors)); + return true; +} + +} + + +BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + BOOST_CHECK(successParse("{ }")); +} + +BOOST_AUTO_TEST_CASE(simple_instructions) +{ + BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }")); +} + +BOOST_AUTO_TEST_CASE(constants) +{ + BOOST_CHECK(successParse("{ 7 8 mul }")); +} + +BOOST_AUTO_TEST_CASE(vardecl) +{ + BOOST_CHECK(successParse("{ let x := 7 }")); +} + +BOOST_AUTO_TEST_CASE(assignment) +{ + BOOST_CHECK(successParse("{ 7 8 add =: x }")); +} + +BOOST_AUTO_TEST_CASE(label) +{ + BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }")); +} + +BOOST_AUTO_TEST_CASE(label_complex) +{ + BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }")); +} + +BOOST_AUTO_TEST_CASE(functional) +{ + BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }")); +} + +BOOST_AUTO_TEST_CASE(functional_assignment) +{ + BOOST_CHECK(successParse("{ x := 7 }")); +} + +BOOST_AUTO_TEST_CASE(functional_assignment_complex) +{ + BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }")); +} + +BOOST_AUTO_TEST_CASE(vardecl_complex) +{ + BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }")); +} + +BOOST_AUTO_TEST_CASE(blocks) +{ + BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces From f0494307232e52dcc268f5f32d26cc89d7e98e3a Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 1 Mar 2016 22:56:39 +0100 Subject: [PATCH 2/3] Code generation (missing external access and source locations). --- libsolidity/analysis/ReferencesResolver.cpp | 22 ++ libsolidity/analysis/ReferencesResolver.h | 1 + libsolidity/analysis/TypeChecker.cpp | 57 +++++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/AST.cpp | 7 + libsolidity/ast/AST.h | 15 +- libsolidity/ast/ASTAnnotations.h | 11 + libsolidity/codegen/Compiler.cpp | 86 +++++++ libsolidity/codegen/Compiler.h | 1 + libsolidity/codegen/CompilerContext.h | 2 + libsolidity/inlineasm/AsmCodeGen.cpp | 263 ++++++++++++++++++++ libsolidity/inlineasm/AsmCodeGen.h | 66 +++++ libsolidity/inlineasm/AsmData.h | 60 ++--- libsolidity/inlineasm/AsmParser.cpp | 67 ++--- libsolidity/inlineasm/AsmParser.h | 21 +- libsolidity/inlineasm/AsmStack.cpp | 47 ++++ libsolidity/inlineasm/AsmStack.h | 59 +++++ libsolidity/parsing/Parser.cpp | 17 +- solc/CommandLineInterface.cpp | 35 ++- solc/CommandLineInterface.h | 6 +- test/libsolidity/InlineAssembly.cpp | 41 ++- test/libsolidity/SolidityEndToEndTest.cpp | 89 +++++++ 22 files changed, 866 insertions(+), 108 deletions(-) create mode 100644 libsolidity/inlineasm/AsmCodeGen.cpp create mode 100644 libsolidity/inlineasm/AsmCodeGen.h create mode 100644 libsolidity/inlineasm/AsmStack.cpp create mode 100644 libsolidity/inlineasm/AsmStack.h diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index ca002f583..d7542bf39 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include using namespace std; using namespace dev; @@ -112,6 +114,26 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) _typeName.annotation().type = make_shared(DataLocation::Storage, baseType); } +bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) +{ + // We need to perform a full code generation pass here as inline assembly does not distinguish + // reference resolution and code generation. + // Errors created in this stage are completely ignored because we do not yet know + // the type and size of external identifiers, which would result in false errors. + ErrorList errorsIgnored; + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); + codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { + auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); + if (declarations.size() != 1) + return false; + _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); + // At this stage we neither know the code to generate nor the stack size of the identifier, + // so we do not modify assembly. + return true; + }); + return false; +} + bool ReferencesResolver::visit(Return const& _return) { _return.annotation().functionReturnParameters = m_returnParameters; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index a2d71dc36..1986b2bb2 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -64,6 +64,7 @@ private: virtual void endVisit(UserDefinedTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; virtual void endVisit(ArrayTypeName const& _typeName) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(Return const& _return) override; virtual void endVisit(VariableDeclaration const& _variable) override; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 44f4629b1..c63b6b5a3 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -24,6 +24,8 @@ #include #include #include +#include // needed for inline assembly +#include using namespace std; using namespace dev; @@ -547,6 +549,61 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return false; } +bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) +{ + // Inline assembly does not have its own type-checking phase, so we just run the + // code-generator and see whether it produces any errors. + // External references have already been resolved in a prior stage and stored in the annotation. + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); + codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return false; + Declaration const* declaration = ref->second; + solAssert(!!declaration, ""); + if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + { + solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); + unsigned pushes = 0; + if (dynamic_cast(declaration)) + pushes = 1; + else if (auto var = dynamic_cast(declaration)) + { + if (var->isConstant()) + fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); + if (var->isLocalVariable()) + pushes = var->type()->sizeOnStack(); + else if (var->type()->isValueType()) + pushes = 1; + else + pushes = 2; // slot number, intra slot offset + } + else if (auto contract = dynamic_cast(declaration)) + { + if (!contract->isLibrary()) + return false; + pushes = 1; + } + for (unsigned i = 0; i < pushes; ++i) + _assembly.append(u256(0)); // just to verify the stack height + } + else + { + // lvalue context + if (auto varDecl = dynamic_cast(declaration)) + { + if (!varDecl->isLocalVariable()) + return false; // only local variables are inline-assemlby lvalues + for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i) + _assembly.append(eth::Instruction::POP); // remove value just to verify the stack height + } + else + return false; + } + return true; + }); + return false; +} bool TypeChecker::visit(IfStatement const& _ifStatement) { diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b884db49d..48f8285a3 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -84,6 +84,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector const& _bases); virtual bool visit(EventDefinition const& _eventDef) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index b5affa8e6..294daa135 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -363,6 +363,13 @@ StatementAnnotation& Statement::annotation() const return static_cast(*m_annotation); } +InlineAssemblyAnnotation& InlineAssembly::annotation() const +{ + if (!m_annotation) + m_annotation = new InlineAssemblyAnnotation(); + return static_cast(*m_annotation); +} + ReturnAnnotation& Return::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index f53c78f2b..7bb2529a1 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -855,8 +855,11 @@ public: virtual StatementAnnotation& annotation() const override; }; -// Forward-declaration to InlineAssembly.h -class AsmData; +namespace assembly +{ +// Forward-declaration to AsmData.h +struct Block; +} /** * Inline assembly. @@ -867,16 +870,18 @@ public: InlineAssembly( SourceLocation const& _location, ASTPointer const& _docString, - std::shared_ptr const& _operations + std::shared_ptr const& _operations ): Statement(_location, _docString), m_operations(_operations) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - AsmData const& operations() const { return *m_operations; } + assembly::Block const& operations() const { return *m_operations; } + + virtual InlineAssemblyAnnotation& annotation() const override; private: - std::shared_ptr m_operations; + std::shared_ptr m_operations; }; /** diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 235338bb8..2a192e47b 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -110,6 +110,17 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation { }; +namespace assembly +{ +struct Identifier; // forward +} + +struct InlineAssemblyAnnotation: StatementAnnotation +{ + /// Mapping containing resolved references to external identifiers. + std::map externalReferences; +}; + struct ReturnAnnotation: StatementAnnotation { /// Reference to the return parameters of the function. diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index c7eb71a8f..69e233599 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -497,6 +498,91 @@ bool Compiler::visit(FunctionDefinition const& _function) return false; } +bool Compiler::visit(InlineAssembly const& _inlineAssembly) +{ + ErrorList errors; + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); + int startStackHeight = m_context.stackHeight(); + m_context.appendInlineAssembly(codeGen.assemble( + [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return false; + Declaration const* decl = ref->second; + solAssert(!!decl, ""); + if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + { + solAssert(!!decl->type(), "Type of declaration required but not yet determined."); + if (/*FunctionDefinition const* functionDef = */dynamic_cast(decl)) + { + solAssert(false, "Referencing local functions in inline assembly not yet implemented."); + // This does not work directly, because the label does not exist in _assembly + // (it is a fresh assembly object). + // _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + } + else if (auto variable = dynamic_cast(decl)) + { + solAssert(!variable->isConstant(), ""); + if (m_context.isLocalVariable(variable)) + { + int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable); + if (stackDiff < 1 || stackDiff > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_comment("Stack too deep, try removing local variables.") + ); + for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) + _assembly.append(eth::dupInstruction(stackDiff)); + } + else + { + solAssert(m_context.isStateVariable(variable), "Invalid variable type."); + auto const& location = m_context.storageLocationOfVariable(*variable); + if (!variable->type()->isValueType()) + { + solAssert(location.second == 0, "Intra-slot offest assumed to be zero."); + _assembly.append(location.first); + } + else + { + _assembly.append(location.first); + _assembly.append(u256(location.second)); + } + } + } + else if (auto contract = dynamic_cast(decl)) + { + solAssert(contract->isLibrary(), ""); + _assembly.appendLibraryAddress(contract->name()); + } + else + solAssert(false, "Invalid declaration type."); + } else { + // lvalue context + auto variable = dynamic_cast(decl); + solAssert( + !!variable || !m_context.isLocalVariable(variable), + "Can only assign to stack variables in inline assembly." + ); + unsigned size = variable->type()->sizeOnStack(); + int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable) - size; + if (stackDiff > 16 || stackDiff < 1) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_comment("Stack too deep, try removing local variables.") + ); + for (unsigned i = 0; i < size; ++i) { + _assembly.append(eth::swapInstruction(stackDiff)); + _assembly.append(eth::Instruction::POP); + } + } + return true; + } + )); + solAssert(errors.empty(), "Code generation for inline assembly with errors requested."); + return false; +} + bool Compiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fa33bd306..68ad904ac 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -94,6 +94,7 @@ private: virtual bool visit(VariableDeclaration const& _variableDeclaration) override; virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 5287088a5..bd8fc295f 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -109,6 +109,8 @@ public: /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } + /// Appends the given code (used by inline assembly) ignoring any stack height changes. + void appendInlineAssembly(eth::Assembly const& _assembly) { int deposit = m_asm.deposit(); m_asm.append(_assembly); m_asm.setDeposit(deposit); } /// Pushes the size of the final program void appendProgramSize() { return m_asm.appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp new file mode 100644 index 000000000..89e95bc14 --- /dev/null +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -0,0 +1,263 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Code-generating part of inline assembly. + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +struct GeneratorState +{ + explicit GeneratorState(ErrorList& _errors): errors(_errors) {} + + void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) + { + auto err = make_shared(_type); + if (!_location.isEmpty()) + *err << errinfo_sourceLocation(_location); + *err << errinfo_comment(_description); + errors.push_back(err); + } + + int const* findVariable(string const& _variableName) const + { + auto localVariable = find_if( + variables.rbegin(), + variables.rend(), + [&](pair const& _var) { return _var.first == _variableName; } + ); + return localVariable != variables.rend() ? &localVariable->second : nullptr; + } + eth::AssemblyItem const* findLabel(string const& _labelName) const + { + auto label = find_if( + labels.begin(), + labels.end(), + [&](pair const& _label) { return _label.first == _labelName; } + ); + return label != labels.end() ? &label->second : nullptr; + } + + eth::Assembly assembly; + map labels; + vector> variables; ///< name plus stack height + ErrorList& errors; +}; + +/** + * Scans the inline assembly data for labels, creates tags in the assembly and searches for + * duplicate labels. + */ +class LabelOrganizer: public boost::static_visitor<> +{ +public: + LabelOrganizer(GeneratorState& _state): m_state(_state) {} + + template + void operator()(T const& /*_item*/) { } + void operator()(Label const& _item) + { + if (m_state.labels.count(_item.name)) + //@TODO location and secondary location + m_state.addError(Error::Type::DeclarationError, "Label " + _item.name + " declared twice."); + m_state.labels.insert(make_pair(_item.name, m_state.assembly.newTag())); + } + void operator()(assembly::Block const& _block) + { + std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); + } + +private: + GeneratorState& m_state; +}; + +class CodeTransform: public boost::static_visitor<> +{ +public: + /// Create the code transformer which appends assembly to _state.assembly when called + /// with parsed assembly data. + /// @param _identifierAccess used to resolve identifiers external to the inline assembly + explicit CodeTransform( + GeneratorState& _state, + assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess() + ): + m_state(_state) + { + if (_identifierAccess) + m_identifierAccess = _identifierAccess; + else + m_identifierAccess = [](assembly::Identifier const&, eth::Assembly&, CodeGenerator::IdentifierContext) { return false; }; + } + + void operator()(Instruction const& _instruction) + { + m_state.assembly.append(_instruction.instruction); + } + void operator()(assembly::Literal const& _literal) + { + if (_literal.isNumber) + m_state.assembly.append(u256(_literal.value)); + else if (_literal.value.size() > 32) + m_state.addError( + Error::Type::TypeError, + "String literal too long (" + boost::lexical_cast(_literal.value.size()) + " > 32)" + ); + else + m_state.assembly.append(_literal.value); + } + void operator()(assembly::Identifier const& _identifier) + { + // First search local variables, then labels, then externals. + if (int const* stackHeight = m_state.findVariable(_identifier.name)) + { + int heightDiff = m_state.assembly.deposit() - *stackHeight; + if (heightDiff <= 0 || heightDiff > 16) + //@TODO location + m_state.addError( + Error::Type::TypeError, + "Variable inaccessible, too deep inside stack (" + boost::lexical_cast(heightDiff) + ")" + ); + else + m_state.assembly.append(eth::dupInstruction(heightDiff)); + return; + } + else if (eth::AssemblyItem const* label = m_state.findLabel(_identifier.name)) + m_state.assembly.append(label->pushTag()); + else if (!m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue)) + m_state.addError( + Error::Type::DeclarationError, + "Identifier \"" + string(_identifier.name) + "\" not found or not unique" + ); + } + void operator()(FunctionalInstruction const& _instr) + { + for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *it); + expectDeposit(1, height); + } + (*this)(_instr.instruction); + } + void operator()(Label const& _label) + { + m_state.assembly.append(m_state.labels.at(_label.name)); + } + void operator()(assembly::Assignment const& _assignment) + { + generateAssignment(_assignment.variableName); + } + void operator()(FunctionalAssignment const& _assignment) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *_assignment.value); + expectDeposit(1, height); + generateAssignment(_assignment.variableName); + } + void operator()(assembly::VariableDeclaration const& _varDecl) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *_varDecl.value); + expectDeposit(1, height); + m_state.variables.push_back(make_pair(_varDecl.name, height)); + } + void operator()(assembly::Block const& _block) + { + size_t numVariables = m_state.variables.size(); + std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); + // pop variables + //@TODO check height before and after + while (m_state.variables.size() > numVariables) + { + m_state.assembly.append(eth::Instruction::POP); + m_state.variables.pop_back(); + } + } + +private: + void generateAssignment(assembly::Identifier const& _variableName) + { + if (int const* stackHeight = m_state.findVariable(_variableName.name)) + { + int heightDiff = m_state.assembly.deposit() - *stackHeight - 1; + if (heightDiff <= 0 || heightDiff > 16) + //@TODO location + m_state.addError( + Error::Type::TypeError, + "Variable inaccessible, too deep inside stack (" + boost::lexical_cast(heightDiff) + ")" + ); + else + { + m_state.assembly.append(eth::swapInstruction(heightDiff)); + m_state.assembly.append(eth::Instruction::POP); + } + return; + } + else if (!m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) + m_state.addError( + Error::Type::DeclarationError, + "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue." + ); + } + + void expectDeposit(int _deposit, int _oldHeight) + { + if (m_state.assembly.deposit() != _oldHeight + 1) + //@TODO location + m_state.addError(Error::Type::TypeError, + "Expected instruction(s) to deposit " + + boost::lexical_cast(_deposit) + + " item(s) to the stack, but did deposit " + + boost::lexical_cast(m_state.assembly.deposit() - _oldHeight) + + " item(s)." + ); + } + + GeneratorState& m_state; + assembly::CodeGenerator::IdentifierAccess m_identifierAccess; +}; + +bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +{ + size_t initialErrorLen = m_errors.size(); + GeneratorState state(m_errors); + (LabelOrganizer(state))(m_parsedData); + (CodeTransform(state, _identifierAccess))(m_parsedData); + return m_errors.size() == initialErrorLen; +} + +eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +{ + GeneratorState state(m_errors); + (LabelOrganizer(state))(m_parsedData); + (CodeTransform(state, _identifierAccess))(m_parsedData); + return state.assembly; +} + diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h new file mode 100644 index 000000000..f749ba502 --- /dev/null +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -0,0 +1,66 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Code-generating part of inline assembly. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace eth +{ +class Assembly; +} +namespace solidity +{ +namespace assembly +{ +struct Block; +struct Identifier; + +class CodeGenerator +{ +public: + enum class IdentifierContext { LValue, RValue }; + /// Function type that is called for external identifiers. Such a function should search for + /// the identifier and append appropriate assembly items to the assembly. If in lvalue context, + /// the value to assign is assumed to be on the stack and an assignment is to be performed. + /// If in rvalue context, the function is assumed to append instructions to + /// push the value of the identifier onto the stack. On error, the function should return false. + using IdentifierAccess = std::function; + CodeGenerator( Block const& _parsedData, ErrorList& _errors): + m_parsedData(_parsedData), m_errors(_errors) {} + /// Performs type checks and @returns false on error. + /// Actually runs the full code generation but discards the result. + bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + /// Performs code generation and @returns the result. + eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + +private: + Block const& m_parsedData; + ErrorList& m_errors; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index a38a9d361..0361a4c2c 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -29,42 +29,36 @@ namespace dev { namespace solidity { - -class AsmData +namespace assembly { -public: - /// Direct EVM instruction (except PUSHi and JUMPDEST) - struct Instruction { eth::Instruction instruction; }; - /// Literal number or string (up to 32 bytes) - struct Literal { bool isNumber; std::string value; }; - /// External / internal identifier or label reference - struct Identifier { std::string name; }; - struct FunctionalInstruction; - /// Jump label ("name:") - struct Label { std::string name; }; - /// Assignemnt (":= x", moves stack top into x, potentially multiple slots) - struct Assignment { Identifier variableName; }; - struct FunctionalAssignment; - struct VariableDeclaration; - struct Block; - using Statement = boost::variant; - /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand - /// side and requires x to occupy exactly one stack slot. - struct FunctionalAssignment { Identifier variableName; std::shared_ptr value; }; - /// Functional instruction, e.g. "mul(mload(20), add(2, x))" - struct FunctionalInstruction { Instruction instruction; std::vector arguments; }; - /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted - struct VariableDeclaration { std::string name; std::shared_ptr value; }; - /// Block that creates a scope (frees declared stack variables) - struct Block { std::vector statements; }; - AsmData(Block&& _statements): m_statements(_statements) {} +/// What follows are the AST nodes for assembly. - Block const& statements() const { return m_statements; } - -private: - Block m_statements; -}; +/// Direct EVM instruction (except PUSHi and JUMPDEST) +struct Instruction { eth::Instruction instruction; }; +/// Literal number or string (up to 32 bytes) +struct Literal { bool isNumber; std::string value; }; +/// External / internal identifier or label reference +struct Identifier { std::string name; }; +struct FunctionalInstruction; +/// Jump label ("name:") +struct Label { std::string name; }; +/// Assignemnt (":= x", moves stack top into x, potentially multiple slots) +struct Assignment { Identifier variableName; }; +struct FunctionalAssignment; +struct VariableDeclaration; +struct Block; +using Statement = boost::variant; +/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand +/// side and requires x to occupy exactly one stack slot. +struct FunctionalAssignment { Identifier variableName; std::shared_ptr value; }; +/// Functional instruction, e.g. "mul(mload(20), add(2, x))" +struct FunctionalInstruction { Instruction instruction; std::vector arguments; }; +/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted +struct VariableDeclaration { std::string name; std::shared_ptr value; }; +/// Block that creates a scope (frees declared stack variables) +struct Block { std::vector statements; }; } } +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 28fd53547..124a5d269 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -29,13 +29,14 @@ using namespace std; using namespace dev; using namespace dev::solidity; +using namespace dev::solidity::assembly; -shared_ptr InlineAssemblyParser::parse(std::shared_ptr const& _scanner) +shared_ptr Parser::parse(std::shared_ptr const& _scanner) { try { m_scanner = _scanner; - return make_shared(parseBlock()); + return make_shared(parseBlock()); } catch (FatalError const&) { @@ -45,17 +46,17 @@ shared_ptr InlineAssemblyParser::parse(std::shared_ptr const& return nullptr; } -AsmData::Block InlineAssemblyParser::parseBlock() +assembly::Block Parser::parseBlock() { expectToken(Token::LBrace); - AsmData::Block block; + Block block; while (m_scanner->currentToken() != Token::RBrace) block.statements.emplace_back(parseStatement()); m_scanner->next(); return block; } -AsmData::Statement InlineAssemblyParser::parseStatement() +assembly::Statement Parser::parseStatement() { switch (m_scanner->currentToken()) { @@ -69,8 +70,9 @@ AsmData::Statement InlineAssemblyParser::parseStatement() expectToken(Token::Colon); string name = m_scanner->currentLiteral(); expectToken(Token::Identifier); - return AsmData::Assignment{AsmData::Identifier{name}}; + return assembly::Assignment{assembly::Identifier{name}}; } + case Token::Return: // opcode default: break; } @@ -78,28 +80,28 @@ AsmData::Statement InlineAssemblyParser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - AsmData::Statement statement(parseElementaryOperation()); + Statement statement(parseElementaryOperation()); switch (m_scanner->currentToken()) { case Token::LParen: return parseFunctionalInstruction(statement); case Token::Colon: { - if (statement.type() != typeid(AsmData::Identifier)) + if (statement.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \":\"."); - string const& name = boost::get(statement).name; + string const& name = boost::get(statement).name; m_scanner->next(); if (m_scanner->currentToken() == Token::Assign) { // functional assignment m_scanner->next(); - unique_ptr value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::FunctionalAssignment{{move(name)}, move(value)}; + unique_ptr value; + value.reset(new Statement(parseExpression())); + return FunctionalAssignment{{std::move(name)}, std::move(value)}; } else // label - return AsmData::Label{name}; + return Label{name}; } default: break; @@ -107,16 +109,16 @@ AsmData::Statement InlineAssemblyParser::parseStatement() return statement; } -AsmData::Statement InlineAssemblyParser::parseExpression() +assembly::Statement Parser::parseExpression() { - AsmData::Statement operation = parseElementaryOperation(true); + Statement operation = parseElementaryOperation(true); if (m_scanner->currentToken() == Token::LParen) return parseFunctionalInstruction(operation); else return operation; } -AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySinglePusher) +assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { // Allowed instructions, lowercase names. static map s_instructions; @@ -129,6 +131,8 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing ) continue; string name = instruction.first; + if (instruction.second == eth::Instruction::SUICIDE) + name = "selfdestruct"; transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); s_instructions[name] = instruction.second; } @@ -138,8 +142,13 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing switch (m_scanner->currentToken()) { case Token::Identifier: + case Token::Return: { - string literal = m_scanner->currentLiteral(); + string literal; + if (m_scanner->currentToken() == Token::Return) + literal = "return"; + else + literal = m_scanner->currentLiteral(); // first search the set of instructions. if (s_instructions.count(literal)) { @@ -151,17 +160,17 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing fatalParserError("Instruction " + info.name + " not allowed in this context."); } m_scanner->next(); - return AsmData::Instruction{instr}; + return Instruction{instr}; } else m_scanner->next(); - return AsmData::Identifier{literal}; + return Identifier{literal}; break; } case Token::StringLiteral: case Token::Number: { - AsmData::Literal literal{ + Literal literal{ m_scanner->currentToken() == Token::Number, m_scanner->currentLiteral() }; @@ -175,23 +184,23 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing return {}; } -AsmData::VariableDeclaration InlineAssemblyParser::parseVariableDeclaration() +assembly::VariableDeclaration Parser::parseVariableDeclaration() { expectToken(Token::Let); string name = m_scanner->currentLiteral(); expectToken(Token::Identifier); expectToken(Token::Colon); expectToken(Token::Assign); - unique_ptr value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::VariableDeclaration{name, move(value)}; + unique_ptr value; + value.reset(new Statement(parseExpression())); + return VariableDeclaration{name, std::move(value)}; } -AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction(AsmData::Statement const& _instruction) +FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement const& _instruction) { - if (_instruction.type() != typeid(AsmData::Instruction)) + if (_instruction.type() != typeid(Instruction)) fatalParserError("Assembly instruction required in front of \"(\")"); - eth::Instruction instr = boost::get(_instruction).instruction; + eth::Instruction instr = boost::get(_instruction).instruction; eth::InstructionInfo instrInfo = eth::instructionInfo(instr); if (eth::Instruction::DUP1 <= instr && instr <= eth::Instruction::DUP16) fatalParserError("DUPi instructions not allowed for functional notation"); @@ -199,7 +208,7 @@ AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction( fatalParserError("SWAPi instructions not allowed for functional notation"); expectToken(Token::LParen); - vector arguments; + vector arguments; unsigned args = unsigned(instrInfo.args); for (unsigned i = 0; i < args; ++i) { @@ -208,5 +217,5 @@ AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction( expectToken(Token::Comma); } expectToken(Token::RParen); - return AsmData::FunctionalInstruction{{instr}, move(arguments)}; + return FunctionalInstruction{{instr}, std::move(arguments)}; } diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index fe84470d3..b54da941b 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -31,25 +31,28 @@ namespace dev { namespace solidity { +namespace assembly +{ -class InlineAssemblyParser: public ParserBase +class Parser: public ParserBase { public: - InlineAssemblyParser(ErrorList& _errors): ParserBase(_errors) {} + Parser(ErrorList& _errors): ParserBase(_errors) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. - std::shared_ptr parse(std::shared_ptr const& _scanner); + std::shared_ptr parse(std::shared_ptr const& _scanner); protected: - AsmData::Block parseBlock(); - AsmData::Statement parseStatement(); + Block parseBlock(); + Statement parseStatement(); /// Parses a functional expression that has to push exactly one stack element - AsmData::Statement parseExpression(); - AsmData::Statement parseElementaryOperation(bool _onlySinglePusher = false); - AsmData::VariableDeclaration parseVariableDeclaration(); - AsmData::FunctionalInstruction parseFunctionalInstruction(AsmData::Statement const& _instruction); + Statement parseExpression(); + Statement parseElementaryOperation(bool _onlySinglePusher = false); + VariableDeclaration parseVariableDeclaration(); + FunctionalInstruction parseFunctionalInstruction(Statement const& _instruction); }; } } +} diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp new file mode 100644 index 000000000..22042adae --- /dev/null +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -0,0 +1,47 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +bool InlineAssemblyStack::parse(const std::shared_ptr& _scanner) +{ + Parser parser(m_errors); + m_asmBlock = parser.parse(_scanner); + return !!m_asmBlock; +} + +eth::Assembly InlineAssemblyStack::assemble() +{ + CodeGenerator codeGen(*m_asmBlock, m_errors); + return codeGen.assemble(); +} + diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h new file mode 100644 index 000000000..73ca95838 --- /dev/null +++ b/libsolidity/inlineasm/AsmStack.h @@ -0,0 +1,59 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace eth +{ +class Assembly; +} +namespace solidity +{ +class Scanner; +namespace assembly +{ +struct Block; + +class InlineAssemblyStack +{ +public: + /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. + /// @return false or error. + bool parse(std::shared_ptr const& _scanner); + eth::Assembly assemble(); + + ErrorList const& errors() const { return m_errors; } + +private: + std::shared_ptr m_asmBlock; + ErrorList m_errors; +}; + +} +} +} diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 29377380c..bb50f47f6 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -735,16 +735,17 @@ ASTPointer Parser::parseInlineAssembly(ASTPointer con { ASTNodeFactory nodeFactory(*this); expectToken(Token::Assembly); - if (m_scanner->currentToken() != Token::StringLiteral) - fatalParserError("Expected assembly name."); - if (m_scanner->currentLiteral() != "evmasm") - fatalParserError("Only \"evmasm\" supported."); - m_scanner->next(); + if (m_scanner->currentToken() == Token::StringLiteral) + { + if (m_scanner->currentLiteral() != "evmasm") + fatalParserError("Only \"evmasm\" supported."); + m_scanner->next(); + } - InlineAssemblyParser parser(m_errors); - shared_ptr operations = parser.parse(m_scanner); + assembly::Parser asmParser(m_errors); + shared_ptr block = asmParser.parse(m_scanner); nodeFactory.markEndPosition(); - return nodeFactory.createNode(_docString, operations); + return nodeFactory.createNode(_docString, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1cf87bc9d..a302e0241 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -773,7 +773,7 @@ void CommandLineInterface::handleAst(string const& _argStr) void CommandLineInterface::actOnInput() { if (m_onlyAssemble) - return; //@TODO + outputAssembly(); else if (m_onlyLink) writeLinkedFiles(); else @@ -831,27 +831,40 @@ bool CommandLineInterface::assemble() { //@TODO later, we will use the convenience interface and should also remove the include above bool successful = true; - ErrorList errors; map> scanners; for (auto const& src: m_sourceCodes) { auto scanner = make_shared(CharStream(src.second), src.first); scanners[src.first] = scanner; - InlineAssemblyParser parser(errors); - if (!parser.parse(scanner)) + if (!m_assemblyStacks[src.first].parse(scanner)) successful = false; + else + //@TODO we should not just throw away the result here + m_assemblyStacks[src.first].assemble(); } - for (auto const& error: errors) - SourceReferenceFormatter::printExceptionInformation( - cerr, - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error", - [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } - ); + for (auto const& stack: m_assemblyStacks) + for (auto const& error: stack.second.errors()) + SourceReferenceFormatter::printExceptionInformation( + cerr, + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } + ); return successful; } +void CommandLineInterface::outputAssembly() +{ + for (auto const& src: m_sourceCodes) + { + cout << endl << "======= " << src.first << " =======" << endl; + eth::Assembly assembly = m_assemblyStacks[src.first].assemble(); + cout << assembly.assemble().toHex() << endl; + cout << assembly.out(); + } +} + void CommandLineInterface::outputCompilationResults() { handleCombinedJSON(); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 1c40e26d0..52854bace 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -21,10 +21,11 @@ */ #pragma once -#include #include #include #include +#include +#include namespace dev { @@ -52,6 +53,7 @@ private: /// Parse assembly input. bool assemble(); + void outputAssembly(); void outputCompilationResults(); @@ -91,6 +93,8 @@ private: std::map m_libraries; /// Solidity compiler stack std::unique_ptr m_compiler; + /// Assembly stacks for assembly-only mode + std::map m_assemblyStacks; }; } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index c435f5d6d..153b3fa8a 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -23,8 +23,9 @@ #include #include #include +#include #include -#include +#include #include #include #include "../TestHelper.h" @@ -40,32 +41,38 @@ namespace test namespace { -shared_ptr parse(std::string const& _source, ErrorList& _errors) -{ - return InlineAssemblyParser(_errors).parse(std::make_shared(CharStream(_source))); -} -bool successParse(std::string const& _source) +bool successParse(std::string const& _source, bool _assemble = false) { - ErrorList errors; + assembly::InlineAssemblyStack stack; try { - auto assembly = parse(_source, errors); - if (!assembly) + if (!stack.parse(std::make_shared(CharStream(_source)))) return false; + if (_assemble) + { + stack.assemble(); + if (!stack.errors().empty()) + return false; + } } catch (FatalError const&) { - if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) return false; } - if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) return false; - BOOST_CHECK(Error::containsOnlyWarnings(errors)); + BOOST_CHECK(Error::containsOnlyWarnings(stack.errors())); return true; } +bool successAssemble(string const& _source) +{ + return successParse(_source, true); +} + } @@ -131,6 +138,16 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_CASE(string_literals) +{ + BOOST_CHECK(successAssemble("{ let x := \"12345678901234567890123456789012\" }")); +} + +BOOST_AUTO_TEST_CASE(oversize_string_literals) +{ + BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 56f074154..663493c9f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6486,6 +6486,95 @@ BOOST_AUTO_TEST_CASE(fixed_bytes_length_access) BOOST_CHECK(callContractFunction("f(bytes32)", "789") == encodeArgs(u256(32), u256(16), u256(8))); } +BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint r, bytes32 r2) { + assembly { r := 7 r2 := "abcdef" } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7), string("abcdef"))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint r) { + for (uint x = 0; x < 10; ++x) + assembly { r := add(r, x) } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(45))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_memory_access) +{ + char const* sourceCode = R"( + contract C { + function test() returns (bytes) { + bytes memory x = new bytes(5); + for (uint i = 0; i < x.length; ++i) + x[i] = byte(i + 1); + assembly { mstore(add(x, 32), "12345678901234567890123456789012") } + return x; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(0x20), u256(5), string("12345"))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_storage_access) +{ + char const* sourceCode = R"( + contract C { + uint16 x; + uint16 public y; + uint public z; + function f() { + // we know that z is aligned because it is too large, so we just discard its + // intra-slot offset value + assembly { 7 z pop sstore } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_jumps) +{ + char const* sourceCode = R"( + contract C { + function f() { + assembly { + let n := calldataload(4) + let a := 1 + let b := a + loop: + jumpi(loopend, eq(n, 0)) + a add swap1 + n := sub(n, 1) + jump(loop) + loopend: + mstore(0, a) + return(0, 0x20) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()", u256(5)) == encodeArgs(u256(13))); + BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34))); +} + BOOST_AUTO_TEST_SUITE_END() } From b336f6261c2373cc769bcafb0466a251576f3fdd Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 22 Mar 2016 15:57:00 +0100 Subject: [PATCH 3/3] Documentation. --- docs/control-structures.rst | 444 ++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 9f0f5b5d2..72cf136d1 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -167,3 +167,447 @@ Currently, there are three situations, where exceptions happen automatically in Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. +.. index:: ! assembly, ! asm, ! evmasm + +Inline Assembly +=============== + +For more fine-grained control especially in order to enhance the language by writing libraries, +it is possible to interleave Solidity statements with inline assembly in a language close +to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is +often hard to address the correct stack slot and provide arguments to opcodes at the correct +point on the stack. Solidity's inline assembly tries to facilitate that and other issues +arising when writing manual assembly by the following features: + +* functional-style opcodes: `mul(1, add(2, 3))` instead of `push1 3 push1 2 add push1 1 mul` +* assembly-local variables: `let x := add(2, 3) let y := mload(0x40) x := add(x, y)` +* access to external variables: `function f(uint x) { assembly { x := sub(x, 1) } }` +* labels: `let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))` + +We now want to describe the inline assembly language in detail. + +.. warning:: + Inline assembly is still a relatively new feature and might change if it does not prove useful, + so please try to keep up to date. + +Example +------- + +The following example provides library code to access the code of another contract and +load it into a `bytes` variable. This is not possible at all with "plain Solidity" and the +idea is that assembly libraries will be used to enhance the language in such ways. + +.. code:: + + library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr); + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), bnot(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } + } + +Inline assemmbly could also be beneficial in cases where the optimizer fails to produce +efficient code. Please be aware that assembly is much more difficult to write because +the compiler does not perform checks, so you should use it for complex things only if +you really know what you are doing. + +.. code:: + + library VectorSum { + // This function is less efficient because the optimizer currently fails to + // remove the bounds checks in array access. + function sumSolidity(uint[] _data) returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) + o_sum += _data[i]; + } + // We know that we only access the array in bounds, so we can avoid the check. + // 0x20 needs to be added to an array because the first slot contains the + // array length. + function sumAsm(uint[] _data) returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) + assembly { o_sum := mload(add(add(_data, 0x20), i)) } + } + } + +Syntax +------ + +Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the +usual `//` and `/* */` comments. Inline assembly is initiated by `assembly { ... }` and inside +these curly braces, the following can be used (see the later sections for more details) + + - literals, i.e. `0x123`, `42` or `"abc"` (strings up to 32 characters) + - opcodes (in "instruction style"), e.g. `mload sload dup1 sstore`, for a list see below + - opcode in functional style, e.g. `add(1, mlod(0))` + - labels, e.g. `name:` + - variable declarations, e.g. `let x := 7` or `let x := add(y, 3)` + - identifiers (externals, labels or assembly-local variables), e.g. `jump(name)`, `3 x add` + - assignments (in "instruction style"), e.g. `3 =: x` + - assignments in functional style, e.g. `x := add(y, 3)` + - blocks where local variables are scoped inside, e.g. `{ let x := 3 { let y := add(x, 1) } }` + +Opcodes +------- + +This document does not want to be a full description of the Ethereum virtual machine, but the +following list can be used as a reference of its opcodes. + +If an opcode takes arguments (always from the top of the stack), they are given in parentheses. +Note that the order of arguments can be seed to be reversed in non-functional style (explained below). +Opcodes marked with `-` do not push an item onto the stack, those marked with `*` are +special and all others push exactly one item onte the stack. + +In the following, `mem[a...b)` signifies the bytes of memory starting at position `a` up to +(excluding) position `b` and `storage[p]` signifies the storage contents at position `p`. + +The opcodes `pushi` and `jumpdest` cannot be used directly. + ++-----------------------+------+---------------------------------------------------------------+ +| stop + `-` | stop execution, identical to return(0,0) | ++-----------------------+------+---------------------------------------------------------------+ +| add(x, y) | | x + y | ++-----------------------+------+---------------------------------------------------------------+ +| sub(x, y) | | x - y | ++-----------------------+------+---------------------------------------------------------------+ +| mul(x, y) | | x * y | ++-----------------------+------+---------------------------------------------------------------+ +| div(x, y) | | x / y | ++-----------------------+------+---------------------------------------------------------------+ +| sdiv(x, y) | | x / y, for signed numbers in two's complement | ++-----------------------+------+---------------------------------------------------------------+ +| mod(x, y) | | x % y | ++-----------------------+------+---------------------------------------------------------------+ +| smod(x, y) | | x % y, for signed numbers in two's complement | ++-----------------------+------+---------------------------------------------------------------+ +| exp(x, y) | | x to the power of y | ++-----------------------+------+---------------------------------------------------------------+ +| bnot(x) | | ~x, every bit of x is negated | ++-----------------------+------+---------------------------------------------------------------+ +| lt(x, y) | | 1 if x < y, 0 otherwise | ++-----------------------+------+---------------------------------------------------------------+ +| gt(x, y) | | 1 if x > y, 0 otherwise | ++-----------------------+------+---------------------------------------------------------------+ +| slt(x, y) | |1 if x < y, 0 otherwise, for signed numbers in two's complement| ++-----------------------+------+---------------------------------------------------------------+ +| sgt(x, y) | |1 if x > y, 0 otherwise, for signed numbers in two's complement| ++-----------------------+------+---------------------------------------------------------------+ +| eq(x, y) | | 1 if x == y, 0 otherwise | ++-----------------------+------+---------------------------------------------------------------+ +| not(x) | | 1 if x == 0, 0 otherwise | ++-----------------------+------+---------------------------------------------------------------+ +| and(x, y) | | bitwise and of x and y | ++-----------------------+------+---------------------------------------------------------------+ +| or(x, y) | | bitwise or of x and y | ++-----------------------+------+---------------------------------------------------------------+ +| xor(x, y) | | bitwise xor of x and y | ++-----------------------+------+---------------------------------------------------------------+ +| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte| ++-----------------------+------+---------------------------------------------------------------+ +| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics | ++-----------------------+------+---------------------------------------------------------------+ +| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics | ++-----------------------+------+---------------------------------------------------------------+ +| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant| ++-----------------------+------+---------------------------------------------------------------+ +| sha3(p, n) | | keccak(mem[p...(p+n))) | ++-----------------------+------+---------------------------------------------------------------+ +| jump(label) | `-` | jump to label / code position | ++-----------------------+------+---------------------------------------------------------------+ +| jumpi(label, cond) | `-` | jump to label if cond is nonzero | ++-----------------------+------+---------------------------------------------------------------+ +| pc | | current position in code | ++-----------------------+------+---------------------------------------------------------------+ +| pop | `*` | remove topmost stack slot | ++-----------------------+------+---------------------------------------------------------------+ +| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) | ++-----------------------+------+---------------------------------------------------------------+ +| swap1 ... swap1 | `*` | swap topmost and ith stack slot below it | ++-----------------------+------+---------------------------------------------------------------+ +| mload(p) | | mem[p..(p+32)) | ++-----------------------+------+---------------------------------------------------------------+ +| mstore(p, v) | `-` | mem[p..(p+32)) := v | ++-----------------------+------+---------------------------------------------------------------+ +| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte | ++-----------------------+------+---------------------------------------------------------------+ +| sload(p) | | storage[p] | ++-----------------------+------+---------------------------------------------------------------+ +| sstore(p, v) | `-` | storage[p] := v | ++-----------------------+------+---------------------------------------------------------------+ +| msize | | size of memory, i.e. largest accessed memory index | ++-----------------------+------+---------------------------------------------------------------+ +| gas | | gas still available to execution | ++-----------------------+------+---------------------------------------------------------------+ +| address | | address of the current contract / execution context | ++-----------------------+------+---------------------------------------------------------------+ +| balance(a) | | wei balance at address a | ++-----------------------+------+---------------------------------------------------------------+ +| caller | | call sender (excluding delegatecall) | ++-----------------------+------+---------------------------------------------------------------+ +| callvalue | | wei sent together with the current call | ++-----------------------+------+---------------------------------------------------------------+ +| calldataload(p) | | call data starting from position p (32 bytes) | ++-----------------------+------+---------------------------------------------------------------+ +| calldatasize | | size of call data in bytes | ++-----------------------+------+---------------------------------------------------------------+ +| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t | ++-----------------------+------+---------------------------------------------------------------+ +| codesize | | size of the code of the current contract / execution context | ++-----------------------+------+---------------------------------------------------------------+ +| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t | ++-----------------------+------+---------------------------------------------------------------+ +| extcodesize(a) | | size of the code at address a | ++-----------------------+------+---------------------------------------------------------------+ +|extcodecopy(a, t, f, s)| `-` | like codecopy(t, f, s) but take code at address a | ++-----------------------+------+---------------------------------------------------------------+ +| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei | +| | | and return the new address | ++-----------------------+------+---------------------------------------------------------------+ +| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)] | +| insize, out, outsize) | | providing g gas and v wei and output area | +| | | mem[out..(out+outsize)] returting 1 on error (out of gas) | ++-----------------------+------+---------------------------------------------------------------+ +| callcode(g, a, v, in, | | identical to call but only use the code from a and stay | +| insize, out, outsize) | | in the context of the current contract otherwise | ++-----------------------+------+---------------------------------------------------------------+ +| delegatecall(g, a, in,| | identical to callcode but also keep `caller` and `callvalue` | +| insize, out, outsize) | | | ++-----------------------+------+---------------------------------------------------------------+ +| return(p, s) | `*` | end execution, return data mem[p..(p+s)) | ++-----------------------+------+---------------------------------------------------------------+ +| selfdestruct(a) | `*` | end execution, destroy current contract and send funds to a | ++-----------------------+------+---------------------------------------------------------------+ +| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) | ++-----------------------+------+---------------------------------------------------------------+ +| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) | ++-----------------------+------+---------------------------------------------------------------+ +| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) | ++-----------------------+------+---------------------------------------------------------------+ +| log3(p, s, t1, t2, t3)| `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) | ++-----------------------+------+---------------------------------------------------------------+ +| log4(p, s, t1, t2, t3,| `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | +| t4) | | | ++-----------------------+------+---------------------------------------------------------------+ +| origin | | transaction sender | ++-----------------------+------+---------------------------------------------------------------+ +| gasprice | | gas price of the transaction | ++-----------------------+------+---------------------------------------------------------------+ +| blockhash(b) | |hash of block nr b - only for last 256 blocks excluding current| ++-----------------------+------+---------------------------------------------------------------+ +| coinbase | | current mining beneficiary | ++-----------------------+------+---------------------------------------------------------------+ +| timestamp | | timestamp of the current block in seconds since the epoch | ++-----------------------+------+---------------------------------------------------------------+ +| number | | current block number | ++-----------------------+------+---------------------------------------------------------------+ +| difficulty | | difficulty of the current block | ++-----------------------+------+---------------------------------------------------------------+ +| gaslimit | | block gas limit of the current block | ++-----------------------+------+---------------------------------------------------------------+ + +Literals +-------- + +You can use integer constants by typing them in decimal or hexadecimal notation and an +appropriate `PUSHi` instruction will automatically be generated. The following creates code +to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc". +Strings are stored left-aligned and cannot be longer than 32 bytes. + +.. code:: + + assembly { 2 3 add "abc" and } + +Functionaly Style +----------------- + +You can type opcode after opcode in the same way they will end up in bytecode. For example +adding `3` to the contents in memory at position `0x80` would be + +.. code:: + + 3 0x80 mload add 0x80 mstore + +As it is often hard to see what the actual arguments for certain opcodes are, +Solidity inline assembly also provides a "functional style" notation where the same code +would be written as follows + +.. code:: + + mstore(0x80, add(mload(0x80), 3)) + +Functional style and instructional style can be mixed, but any opcode inside a +functional style expression has to return exactly one stack slot (most of the opcodes do). + +Note that the order of arguments is reversed in functional-style as opposed to the instruction-style +way. If you use functional-style, the first argument will end up on the stack top. + + +Access to External Variables +---------------------------- + +Solidity variables and other identifiers can be accessed by simply using their name. +For storage and memory variables, this will push the address and not the value onto the +stack. Also note that non-struct and non-array storage variable addresses occupy two slots +on the stack: One for the address and one for the byte offset inside the storage slot. +In assignments (see below), we can even use local Solidity variables to assign to. + +.. code:: + + contract c { + uint b; + function f(uint x) returns (uint r) { + assembly { + b pop // remove the offset, we know it is zero + sload + x + mul + =: r // assign to return variable r + } + } + } + +Labels +------ + +Another problem in EVM assembly is that `jump` and `jumpi` use absolute addresses +which can change easily. Solidity inline assembly provides labels to make the use of +jumps easier. The following code computes an element in the Fibonacci series. + +.. code:: + + { + let n := calldataload(4) + let a := 1 + let b := a + loop: + jumpi(loopend, eq(n, 0)) + a add swap1 + n := sub(n, 1) + jump(loop) + loopend: + mstore(0, a) + return(0, 0x20) + } + +Please note that automatically accessing stack variables can only work if the +assembler knows the current stack height. This fails to work if the jump source +and target have different stack heights. It is still fine to use such jumps, +you should just not access any stack variables (even assembly variables) in that case. + +Furthermore, the stack height analyser goes through the code opcode by opcode +(and not according to control flow), so in the following case, the assembler +will have a wrong impression about the stack height at label `two`: + +.. code:: + + { + jump(two) + one: + // Here the stack height is 1 (because we pushed 7), + // but the assembler thinks it is 0 because it reads + // from top to bottom. + // Accessing stack variables here will lead to errors. + jump(three) + two: + 7 // push something onto the stack + jump(one) + three: + } + + +Declaring Assembly-Local Variables +---------------------------------- + +You can use the `let` keyword to declare variables that are only visible in +inline assembly and actually only in the current `{...}`-block. What happens +is that the `let` instruction will create a new stack slot that is reserved +for the variable and automatically removed again when the end of the block +is reached. You need to provide an initial value for the variable which can +be just `0`, but it can also be a complex functional-style expression. + +.. code:: + + contract c { + function f(uint x) returns (uint b) { + assembly { + let v := add(x, 1) + mstore(0x80, v) + { + let y := add(sload(v), 1) + b := y + } // y is "deallocated" here + b := add(b, v) + } // v is "deallocated" here + } + } + + +Assignments +----------- + +Assignments are possible to assembly-local variables and to function-local +variables. Take care that when you assign to variables that point to +memory or storage, you will only change the pointer and not the data. + +There are two kinds of assignments: Functional-style and instruction-style. +For functionaly-style assignments (`variable := value`), you need to provide a value in a +functional-style expression that results in exactly one stack value +and for instruction-style (`=: variable`), the value is just taken from the stack top. +For both ways, the colon points to the name of the variable. + +.. code:: + + assembly { + let v := 0 // functional-style assignment as part of variable declaration + let g := add(v, 2) + sload(10) + := v // instruction style assignment, puts the result of sload(10) into v + } + + +Things to Avoid +--------------- + +Inline assembly might have a quite high-level look, but it actually is extremely +low-level. The only thing the assembler does for you is re-arranging +functional-style opcodes, managing jump labels, counting stack height for +variable access and removing stack slots for assembly-local variables when the end +of their block is reached. Especially for those two last cases, it is important +to know that the assembler only counts stack height from top to bottom, not +necessarily following control flow. Furthermore, operations like swap will only +swap the contents of the stack but not the location of variables. + +Conventions in Solidity +----------------------- + +In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, +e.g. `uint24`. In order to make them more efficient, most arithmetic operations just +treat them as 256 bit numbers and the higher-order bits are only cleaned at the +point where it is necessary, i.e. just shortly before they are written to memory +or before comparisons are performed. This means that if you access such a variable +from within inline assembly, you might have to manually clean the higher order bits +first. + +Solidity manages memory in a very simple way: There is a "free memory pointer" +at position `0x40` in memory. If you want to allocate memory, just use the memory +from that point on and update the pointer accordingly. + +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is +even true for `byte[]`, but not for `bytes` and `string`). Multi-dimensional memory +arrays are pointers to memory arrays. The length of a dynamic array is stored at the +first slot of the array and then only the array elements follow. + +.. warning:: + Statically-sized memory arrays do not have a length field, but it will be added soon + to allow better convertibility between statically- and dynamically-sized arrays, so + please do not rely on that.