mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #2225 from ethereum/julia-for
Implement for statement in assembly parser / printer / code generator
This commit is contained in:
		
						commit
						0c75afb2c1
					
				| @ -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. | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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, ""); | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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>; | ||||
|  | ||||
| @ -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); | ||||
| } | ||||
|  | ||||
| @ -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> | ||||
| { | ||||
|  | ||||
							
								
								
									
										52
									
								
								libsolidity/inlineasm/AsmDataForward.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								libsolidity/inlineasm/AsmDataForward.h
									
									
									
									
									
										Normal file
									
								
							| @ -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>; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
| @ -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); | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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"({ | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user