diff options
-rw-r--r-- | Changelog.md | 2 | ||||
-rw-r--r-- | docs/assembly.rst | 40 | ||||
-rw-r--r-- | libjulia/backends/evm/EVMCodeTransform.cpp | 89 | ||||
-rw-r--r-- | libjulia/backends/evm/EVMCodeTransform.h | 26 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmAnalysis.cpp | 31 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmAnalysis.h | 17 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmAnalysisInfo.h | 17 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmCodeGen.cpp | 2 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmData.h | 23 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmDataForward.h | 52 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.cpp | 16 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.h | 1 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.cpp | 13 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.h | 15 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScope.cpp | 9 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScope.h | 2 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScopeFiller.cpp | 20 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmScopeFiller.h | 16 | ||||
-rw-r--r-- | libsolidity/interface/AssemblyStack.cpp | 2 | ||||
-rw-r--r-- | test/libsolidity/InlineAssembly.cpp | 48 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 49 |
21 files changed, 347 insertions, 143 deletions
diff --git a/Changelog.md b/Changelog.md index de65421a..0fb2fe5c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,8 @@ Features: * Inline Assembly: Present proper error message when not supplying enough arguments to a functional instruction. * Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias. + * Inline Assembly: ``for`` and ``switch`` statements. + * Inline Assembly: function definitions and function calls. Bugfixes: * Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences. diff --git a/docs/assembly.rst b/docs/assembly.rst index 394fc9f5..7ef41483 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -28,11 +28,8 @@ arising when writing manual assembly by the following features: * 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))`` * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` -* switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }`` -* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` - -.. note:: - Of the above, loops, function calls and switch statements are not yet implemented. +* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }`` +* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }`` We now want to describe the inline assembly language in detail. @@ -501,9 +498,6 @@ is performed by replacing the variable's value on the stack by the new value. Switch ------ -.. note:: - Switch is not yet implemented. - You can use a switch statement as a very basic version of "if/else". It takes the value of an expression and compares it to several constants. The branch corresponding to the matching constant is taken. Contrary to the @@ -516,10 +510,10 @@ case called ``default``. assembly { let x := 0 switch calldataload(4) - case 0: { + case 0 { x := calldataload(0x24) } - default: { + default { x := calldataload(0x44) } sstore(0, div(x, 2)) @@ -531,13 +525,10 @@ case does require them. Loops ----- -.. note:: - Loops are not yet implemented. - Assembly supports a simple for-style loop. For-style loops have a header containing an initializing part, a condition and a post-iteration part. The condition has to be a functional-style expression, while -the other two can also be blocks. If the initializing part is a block that +the other two are blocks. If the initializing part declares any variables, the scope of these variables is extended into the body (including the condition and the post-iteration part). @@ -555,9 +546,6 @@ The following example computes the sum of an area in memory. Functions --------- -.. note:: - Functions are not yet implemented. - Assembly allows the definition of low-level functions. These take their arguments (and a return PC) from the stack and also put the results onto the stack. Calling a function looks the same way as executing a functional-style @@ -569,7 +557,7 @@ defined outside of that function. There is no explicit ``return`` statement. If you call a function that returns multiple values, you have to assign -them to a tuple using ``(a, b) := f(x)`` or ``let (a, b) := f(x)``. +them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``. The following example implements the power function by square-and-multiply. @@ -578,12 +566,12 @@ The following example implements the power function by square-and-multiply. assembly { function power(base, exponent) -> result { switch exponent - 0: { result := 1 } - 1: { result := base } - default: { + case 0 { result := 1 } + case 1 { result := base } + default { result := power(mul(base, base), div(exponent, 2)) switch mod(exponent, 2) - 1: { result := mul(base, result) } + case 1 { result := mul(base, result) } } } } @@ -703,13 +691,13 @@ The following assembly will be generated:: mstore(0x40, 0x60) // store the "free memory pointer" // function dispatcher switch div(calldataload(0), exp(2, 226)) - case 0xb3de648b: { + case 0xb3de648b { let (r) = f(calldataload(4)) let ret := $allocate(0x20) mstore(ret, r) return(ret, 0x20) } - default: { revert(0, 0) } + default { revert(0, 0) } // memory allocator function $allocate(size) -> pos { pos := mload(0x40) @@ -860,8 +848,8 @@ Grammar:: AssemblyAssignment = '=:' Identifier LabelDefinition = Identifier ':' AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* - ( 'default' ':' AssemblyBlock )? - AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock + ( 'default' AssemblyBlock )? + AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' ( '->' '(' IdentifierList ')' )? AssemblyBlock AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression) diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 7c14eb8b..b231ecec 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -33,26 +33,6 @@ using namespace dev::julia; using namespace dev::solidity; using namespace dev::solidity::assembly; -void CodeTransform::run(Block const& _block) -{ - m_scope = m_info.scopes.at(&_block).get(); - - int blockStartStackHeight = m_assembly.stackHeight(); - std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); - - m_assembly.setSourceLocation(_block.location); - - // pop variables - for (auto const& identifier: m_scope->identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) - m_assembly.appendInstruction(solidity::Instruction::POP); - - int deposit = m_assembly.stackHeight() - blockStartStackHeight; - solAssert(deposit == 0, "Invalid stack height at end of block."); - checkStackHeight(&_block); -} - - void CodeTransform::operator()(VariableDeclaration const& _varDecl) { solAssert(m_scope, ""); @@ -315,7 +295,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, localStackAdjustment, m_context) - .run(_function.body); + (_function.body); { // The stack layout here is: @@ -358,9 +338,54 @@ void CodeTransform::operator()(FunctionDefinition const& _function) checkStackHeight(&_function); } +void CodeTransform::operator()(ForLoop const& _forLoop) +{ + Scope* originalScope = m_scope; + // We start with visiting the block, but not finalizing it. + m_scope = m_info.scopes.at(&_forLoop.pre).get(); + int stackStartHeight = m_assembly.stackHeight(); + + visitStatements(_forLoop.pre.statements); + + // TODO: When we implement break and continue, the labels and the stack heights at that point + // have to be stored in a stack. + AbstractAssembly::LabelID loopStart = m_assembly.newLabelId(); + AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); + AbstractAssembly::LabelID postPart = m_assembly.newLabelId(); + + m_assembly.setSourceLocation(_forLoop.location); + m_assembly.appendLabel(loopStart); + + visitExpression(*_forLoop.condition); + m_assembly.setSourceLocation(_forLoop.location); + m_assembly.appendInstruction(solidity::Instruction::ISZERO); + m_assembly.appendJumpToIf(loopEnd); + + (*this)(_forLoop.body); + + m_assembly.setSourceLocation(_forLoop.location); + m_assembly.appendLabel(postPart); + + (*this)(_forLoop.post); + + m_assembly.setSourceLocation(_forLoop.location); + m_assembly.appendJumpTo(loopStart); + m_assembly.appendLabel(loopEnd); + + finalizeBlock(_forLoop.pre, stackStartHeight); + m_scope = originalScope; +} + void CodeTransform::operator()(Block const& _block) { - CodeTransform(m_assembly, m_info, m_evm15, m_identifierAccess, m_stackAdjustment, m_context).run(_block); + Scope* originalScope = m_scope; + m_scope = m_info.scopes.at(&_block).get(); + + int blockStartStackHeight = m_assembly.stackHeight(); + visitStatements(_block.statements); + + finalizeBlock(_block, blockStartStackHeight); + m_scope = originalScope; } AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier) @@ -401,6 +426,26 @@ void CodeTransform::visitExpression(Statement const& _expression) expectDeposit(1, height); } +void CodeTransform::visitStatements(vector<Statement> const& _statements) +{ + for (auto const& statement: _statements) + boost::apply_visitor(*this, statement); +} + +void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight) +{ + m_assembly.setSourceLocation(_block.location); + + // pop variables + solAssert(m_info.scopes.at(&_block).get() == m_scope, ""); + for (size_t i = 0; i < m_scope->numberOfVariables(); ++i) + m_assembly.appendInstruction(solidity::Instruction::POP); + + int deposit = m_assembly.stackHeight() - blockStartStackHeight; + solAssert(deposit == 0, "Invalid stack height at end of block."); + checkStackHeight(&_block); +} + void CodeTransform::generateAssignment(Identifier const& _variableName) { solAssert(m_scope, ""); diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index 202f5051..d09ee87b 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -21,6 +21,7 @@ #include <libjulia/backends/evm/EVMAssembly.h> #include <libsolidity/inlineasm/AsmScope.h> +#include <libsolidity/inlineasm/AsmDataForward.h> #include <boost/variant.hpp> #include <boost/optional.hpp> @@ -32,21 +33,6 @@ namespace solidity class ErrorReporter; namespace assembly { -struct Literal; -struct Block; -struct Switch; -struct Label; -struct FunctionalInstruction; -struct Assignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct StackAssignment; -struct FunctionDefinition; -struct FunctionCall; - -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>; - struct AsmAnalysisInfo; } } @@ -75,9 +61,6 @@ public: { } - /// Processes the block and appends the resulting code to the assembly. - void run(solidity::assembly::Block const& _block); - protected: struct Context { @@ -115,6 +98,7 @@ public: void operator()(solidity::assembly::VariableDeclaration const& _varDecl); void operator()(solidity::assembly::Switch const& _switch); void operator()(solidity::assembly::FunctionDefinition const&); + void operator()(solidity::assembly::ForLoop const&); void operator()(solidity::assembly::Block const& _block); private: @@ -126,6 +110,12 @@ private: /// Generates code for an expression that is supposed to return a single value. void visitExpression(solidity::assembly::Statement const& _expression); + void visitStatements(std::vector<solidity::assembly::Statement> const& _statements); + + /// Pops all variables declared in the block and checks that the stack height is equal + /// to @a _blackStartStackHeight. + void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight); + void generateAssignment(solidity::assembly::Identifier const& _variableName); /// Determines the stack height difference to the given variables. Throws diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 1a529118..2891ec95 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -310,6 +310,33 @@ bool AsmAnalyzer::operator()(Switch const& _switch) return success; } +bool AsmAnalyzer::operator()(assembly::ForLoop const& _for) +{ + Scope* originalScope = m_currentScope; + + bool success = true; + if (!(*this)(_for.pre)) + success = false; + // The block was closed already, but we re-open it again and stuff the + // condition, the body and the post part inside. + m_stackHeight += scope(&_for.pre).numberOfVariables(); + m_currentScope = &scope(&_for.pre); + + if (!expectExpression(*_for.condition)) + success = false; + m_stackHeight--; + if (!(*this)(_for.body)) + success = false; + if (!(*this)(_for.post)) + success = false; + + m_stackHeight -= scope(&_for.pre).numberOfVariables(); + m_info.stackHeightInfo[&_for] = m_stackHeight; + m_currentScope = originalScope; + + return success; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; @@ -322,9 +349,7 @@ bool AsmAnalyzer::operator()(Block const& _block) if (!boost::apply_visitor(*this, s)) success = false; - for (auto const& identifier: scope(&_block).identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) - --m_stackHeight; + m_stackHeight -= scope(&_block).numberOfVariables(); int const stackDiff = m_stackHeight - initialStackHeight; if (stackDiff != 0) diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 2516722a..76d2eba1 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -26,6 +26,8 @@ #include <libjulia/backends/evm/AbstractAssembly.h> +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> #include <functional> @@ -39,20 +41,6 @@ class ErrorReporter; namespace assembly { -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct Assignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct StackAssignment; -struct FunctionDefinition; -struct FunctionCall; -struct Switch; -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>; - struct AsmAnalysisInfo; /** @@ -83,6 +71,7 @@ public: bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const& _functionCall); bool operator()(assembly::Switch const& _switch); + bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); private: diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h index 78c1fbe0..bd3b28c4 100644 --- a/libsolidity/inlineasm/AsmAnalysisInfo.h +++ b/libsolidity/inlineasm/AsmAnalysisInfo.h @@ -20,6 +20,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> #include <map> @@ -33,23 +35,8 @@ namespace solidity namespace assembly { -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct Assignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct StackAssignment; -struct FunctionDefinition; -struct FunctionCall; -struct Switch; - struct Scope; -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>; - struct AsmAnalysisInfo { using StackHeightInfo = std::map<void const*, int>; diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 27750453..3c7c62c6 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -141,5 +141,5 @@ void assembly::CodeGenerator::assemble( ) { EthAssemblyAdapter assemblyAdapter(_assembly); - julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess).run(_parsedData); + julia::CodeTransform(assemblyAdapter, _analysisInfo, false, _identifierAccess)(_parsedData); } diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index 72afeef1..db5840bc 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -22,10 +22,13 @@ #pragma once -#include <boost/variant.hpp> +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <libevmasm/Instruction.h> #include <libevmasm/SourceLocation.h> +#include <boost/variant.hpp> + namespace dev { namespace solidity @@ -38,23 +41,6 @@ using Type = std::string; struct TypedName { SourceLocation location; std::string name; Type type; }; using TypedNameList = std::vector<TypedName>; -/// What follows are the AST nodes for assembly. - -struct Instruction; -struct Literal; -struct Label; -struct StackAssignment; -struct Identifier; -struct Assignment; -struct VariableDeclaration; -struct FunctionalInstruction; -struct FunctionDefinition; -struct FunctionCall; -struct Switch; -struct Block; - -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>; - /// Direct EVM instruction (except PUSHi and JUMPDEST) struct Instruction { SourceLocation location; solidity::Instruction instruction; }; /// Literal number or string (up to 32 bytes) @@ -82,6 +68,7 @@ struct FunctionDefinition { SourceLocation location; std::string name; TypedName struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; }; /// Switch statement struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; }; +struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; }; struct LocationExtractor: boost::static_visitor<SourceLocation> { diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h new file mode 100644 index 00000000..4ead7ff5 --- /dev/null +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Forward declaration of classes for inline assembly / JULIA AST + */ + +#pragma once + +#include <boost/variant.hpp> + +namespace dev +{ +namespace solidity +{ +namespace assembly +{ + +struct Instruction; +struct Literal; +struct Label; +struct StackAssignment; +struct Identifier; +struct Assignment; +struct VariableDeclaration; +struct FunctionalInstruction; +struct FunctionDefinition; +struct FunctionCall; +struct Switch; +struct ForLoop; +struct Block; + +using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>; + +} +} +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index f9b073ba..d282a30d 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -87,6 +87,8 @@ assembly::Statement Parser::parseStatement() _switch.location.end = _switch.cases.back().body.location.end; return _switch; } + case Token::For: + return parseForLoop(); case Token::Assign: { if (m_julia) @@ -171,6 +173,20 @@ assembly::Case Parser::parseCase() return _case; } +assembly::ForLoop Parser::parseForLoop() +{ + ForLoop forLoop = createWithLocation<ForLoop>(); + expectToken(Token::For); + forLoop.pre = parseBlock(); + forLoop.condition = make_shared<Statement>(parseExpression()); + if (forLoop.condition->type() == typeid(assembly::Instruction)) + fatalParserError("Instructions are not supported as conditions for the for statement."); + forLoop.post = parseBlock(); + forLoop.body = parseBlock(); + forLoop.location.end = forLoop.body.location.end; + return forLoop; +} + assembly::Statement Parser::parseExpression() { Statement operation = parseElementaryOperation(true); diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 5fafad23..45708afd 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -63,6 +63,7 @@ protected: Block parseBlock(); Statement parseStatement(); Case parseCase(); + ForLoop parseForLoop(); /// Parses a functional expression that has to push exactly one stack element Statement parseExpression(); static std::map<std::string, dev::solidity::Instruction> const& instructions(); diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index e282e5e8..0d06fedd 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -181,6 +181,19 @@ string AsmPrinter::operator()(Switch const& _switch) return out; } +string AsmPrinter::operator()(assembly::ForLoop const& _forLoop) +{ + string out = "for "; + out += (*this)(_forLoop.pre); + out += "\n"; + out += boost::apply_visitor(*this, *_forLoop.condition); + out += "\n"; + out += (*this)(_forLoop.post); + out += "\n"; + out += (*this)(_forLoop.body); + return out; +} + string AsmPrinter::operator()(Block const& _block) { if (_block.statements.empty()) diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index b0d7fc09..f57dddc8 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> namespace dev @@ -30,18 +32,6 @@ namespace solidity { namespace assembly { -struct Instruction; -struct Literal; -struct Identifier; -struct FunctionalInstruction; -struct Label; -struct StackAssignment; -struct Assignment; -struct VariableDeclaration; -struct FunctionDefinition; -struct FunctionCall; -struct Switch; -struct Block; class AsmPrinter: public boost::static_visitor<std::string> { @@ -59,6 +49,7 @@ public: std::string operator()(assembly::FunctionDefinition const& _functionDefinition); std::string operator()(assembly::FunctionCall const& _functionCall); std::string operator()(assembly::Switch const& _switch); + std::string operator()(assembly::ForLoop const& _forLoop); std::string operator()(assembly::Block const& _block); private: diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp index 1db5ca41..315d5953 100644 --- a/libsolidity/inlineasm/AsmScope.cpp +++ b/libsolidity/inlineasm/AsmScope.cpp @@ -80,6 +80,15 @@ bool Scope::exists(string const& _name) return false; } +size_t Scope::numberOfVariables() const +{ + size_t count = 0; + for (auto const& identifier: identifiers) + if (identifier.second.type() == typeid(Scope::Variable)) + count++; + return count; +} + bool Scope::insideFunction() const { for (Scope const* s = this; s; s = s->superScope) diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h index de9119e0..cc240565 100644 --- a/libsolidity/inlineasm/AsmScope.h +++ b/libsolidity/inlineasm/AsmScope.h @@ -109,6 +109,8 @@ struct Scope /// across function and assembly boundaries). bool exists(std::string const& _name); + /// @returns the number of variables directly registered inside the scope. + size_t numberOfVariables() const; /// @returns true if this scope is inside a function. bool insideFunction() const; diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index 4d26dcf8..3bef9cec 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -111,6 +111,26 @@ bool ScopeFiller::operator()(Switch const& _switch) return success; } +bool ScopeFiller::operator()(ForLoop const& _forLoop) +{ + Scope* originalScope = m_currentScope; + + bool success = true; + if (!(*this)(_forLoop.pre)) + success = false; + m_currentScope = &scope(&_forLoop.pre); + if (!boost::apply_visitor(*this, *_forLoop.condition)) + success = false; + if (!(*this)(_forLoop.body)) + success = false; + if (!(*this)(_forLoop.post)) + success = false; + + m_currentScope = originalScope; + + return success; +} + bool ScopeFiller::operator()(Block const& _block) { bool success = true; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index 1166d50f..80c03d2c 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -20,6 +20,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmDataForward.h> + #include <boost/variant.hpp> #include <functional> @@ -35,19 +37,6 @@ namespace assembly { struct TypedName; -struct Literal; -struct Block; -struct Label; -struct FunctionalInstruction; -struct Assignment; -struct VariableDeclaration; -struct Instruction; -struct Identifier; -struct StackAssignment; -struct FunctionDefinition; -struct FunctionCall; -struct Switch; - struct Scope; struct AsmAnalysisInfo; @@ -71,6 +60,7 @@ public: bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const&) { return true; } bool operator()(assembly::Switch const& _switch); + bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); private: diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 7dc1edc7..2d85895e 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -100,7 +100,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { MachineAssemblyObject object; julia::EVMAssembly assembly(true); - julia::CodeTransform(assembly, *m_analysisInfo, true).run(*m_parserResult); + julia::CodeTransform(assembly, *m_analysisInfo, true)(*m_parserResult); object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); /// TOOD: fill out text representation return object; diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index aae6dacd..7b760a1d 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -269,6 +269,7 @@ BOOST_AUTO_TEST_CASE(switch_invalid_expression) { CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch."); + CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } BOOST_AUTO_TEST_CASE(switch_default_before_case) @@ -291,6 +292,41 @@ BOOST_AUTO_TEST_CASE(switch_invalid_body) CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'"); } +BOOST_AUTO_TEST_CASE(for_statement) +{ + BOOST_CHECK(successParse("{ for {} 1 {} {} }")); + BOOST_CHECK(successParse("{ for { let i := 1 } lt(i, 5) { i := add(i, 1) } {} }")); +} + +BOOST_AUTO_TEST_CASE(for_invalid_expression) +{ + CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected."); + CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'"); + CHECK_PARSE_ERROR("{ for {} calldatasize {} {} }", ParserError, "Instructions are not supported as conditions for the for statement."); + CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); +} + +BOOST_AUTO_TEST_CASE(for_visibility) +{ + BOOST_CHECK(successParse("{ for { let i := 1 } i { pop(i) } { pop(i) } }")); + CHECK_PARSE_ERROR("{ for {} i { let i := 1 } {} }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for {} 1 { let i := 1 } { pop(i) } }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for { pop(i) } 1 { let i := 1 } {} }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for { pop(i) } 1 { } { let i := 1 } }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for {} i {} { let i := 1 } }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found"); + CHECK_PARSE_ERROR("{ for { let x := 1 } 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope"); + CHECK_PARSE_ERROR("{ for { let x := 1 } 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope"); + CHECK_PARSE_ERROR("{ let x := 1 for { let x := 1 } 1 {} {} }", DeclarationError, "Variable name x already taken in this scope"); + CHECK_PARSE_ERROR("{ let x := 1 for {} 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope"); + CHECK_PARSE_ERROR("{ let x := 1 for {} 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope"); + // Check that body and post are not sub-scopes of each other. + BOOST_CHECK(successParse("{ for {} 1 { let x := 1 } { let x := 1 } }")); +} + BOOST_AUTO_TEST_CASE(blocks) { BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); @@ -409,6 +445,11 @@ BOOST_AUTO_TEST_CASE(print_switch) parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}"); } +BOOST_AUTO_TEST_CASE(print_for) +{ + parsePrintCompare("{\n let ret := 5\n for {\n let i := 1\n }\n lt(i, 15)\n {\n i := add(i, 1)\n }\n {\n ret := mul(ret, i)\n }\n}"); +} + BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) { parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}"); @@ -534,6 +575,13 @@ BOOST_AUTO_TEST_CASE(switch_statement) BOOST_CHECK(successAssemble("{ let a := 2 switch calldataload(0) case 1 { a := 1 } case 2 { a := 5 } }")); } +BOOST_AUTO_TEST_CASE(for_statement) +{ + BOOST_CHECK(successAssemble("{ for {} 1 {} {} }")); + BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }")); +} + + BOOST_AUTO_TEST_CASE(large_constant) { auto source = R"({ diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 52ce65f1..ba507e0c 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7685,6 +7685,55 @@ BOOST_AUTO_TEST_CASE(inline_assembly_recursion) BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24))); } +BOOST_AUTO_TEST_CASE(inline_assembly_for) +{ + char const* sourceCode = R"( + contract C { + function f(uint a) returns (uint b) { + assembly { + function fac(n) -> nf { + nf := 1 + for { let i := n } gt(i, 0) { i := sub(i, 1) } { + nf := mul(nf, i) + } + } + b := fac(a) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(2))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_for2) +{ + char const* sourceCode = R"( + contract C { + uint st; + function f(uint a) returns (uint b, uint c, uint d) { + st = 0; + assembly { + function sideeffect(r) -> x { sstore(0, add(sload(0), r)) x := 1} + for { let i := a } eq(i, sideeffect(2)) { d := add(d, 3) } { + b := i + i := 0 + } + } + c = st; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(0), u256(2), u256(0))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1), u256(4), u256(3))); + BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(0), u256(2), u256(0))); +} + BOOST_AUTO_TEST_CASE(index_access_with_type_conversion) { // Test for a bug where higher order bits cleanup was not done for array index access. |