mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #545 from chriseth/accessFunLabels
Allow access to functions in inline assembly.
This commit is contained in:
		
						commit
						775b757d0e
					
				| @ -509,8 +509,8 @@ Note that the order of arguments is reversed in functional-style as opposed to t | |||||||
| way. If you use functional-style, the first argument will end up on the stack top. | way. If you use functional-style, the first argument will end up on the stack top. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Access to External Variables | Access to External Variables and Functions | ||||||
| ---------------------------- | ------------------------------------------ | ||||||
| 
 | 
 | ||||||
| Solidity variables and other identifiers can be accessed by simply using their name. | 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 | For storage and memory variables, this will push the address and not the value onto the | ||||||
| @ -518,6 +518,17 @@ stack. Also note that non-struct and non-array storage variable addresses occupy | |||||||
| on the stack: One for the address and one for the byte offset inside the storage slot. | 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. | In assignments (see below), we can even use local Solidity variables to assign to. | ||||||
| 
 | 
 | ||||||
|  | Functions external to inline assembly can also be accessed: The assembly will | ||||||
|  | push their entry label (with virtual function resolution applied). The calling semantics | ||||||
|  | in solidity are: | ||||||
|  | 
 | ||||||
|  |  - the caller pushes return label, arg1, arg2, ..., argn | ||||||
|  |  - the call returns with ret1, ret2, ..., retn | ||||||
|  | 
 | ||||||
|  | This feature is still a bit cumbersome to use, because the stack offset essentially | ||||||
|  | changes during the call, and thus references to local variables will be wrong. | ||||||
|  | It is planned that the stack height changes can be specified in inline assembly. | ||||||
|  | 
 | ||||||
| .. code:: | .. code:: | ||||||
| 
 | 
 | ||||||
| 	contract c { | 	contract c { | ||||||
|  | |||||||
| @ -502,8 +502,9 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly) | |||||||
| { | { | ||||||
| 	ErrorList errors; | 	ErrorList errors; | ||||||
| 	assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); | 	assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); | ||||||
| 	int startStackHeight = m_context.stackHeight(); | 	unsigned startStackHeight = m_context.stackHeight(); | ||||||
| 	m_context.appendInlineAssembly(codeGen.assemble( | 	codeGen.assemble( | ||||||
|  | 		m_context.nonConstAssembly(), | ||||||
| 		[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { | 		[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { | ||||||
| 			auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | 			auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | ||||||
| 			if (ref == _inlineAssembly.annotation().externalReferences.end()) | 			if (ref == _inlineAssembly.annotation().externalReferences.end()) | ||||||
| @ -513,19 +514,14 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly) | |||||||
| 			if (_context == assembly::CodeGenerator::IdentifierContext::RValue) | 			if (_context == assembly::CodeGenerator::IdentifierContext::RValue) | ||||||
| 			{ | 			{ | ||||||
| 				solAssert(!!decl->type(), "Type of declaration required but not yet determined."); | 				solAssert(!!decl->type(), "Type of declaration required but not yet determined."); | ||||||
| 				if (/*FunctionDefinition const* functionDef = */dynamic_cast<FunctionDefinition const*>(decl)) | 				if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) | ||||||
| 				{ | 					_assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); | ||||||
| 					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<VariableDeclaration const*>(decl)) | 				else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) | ||||||
| 				{ | 				{ | ||||||
| 					solAssert(!variable->isConstant(), ""); | 					solAssert(!variable->isConstant(), ""); | ||||||
| 					if (m_context.isLocalVariable(variable)) | 					if (m_context.isLocalVariable(variable)) | ||||||
| 					{ | 					{ | ||||||
| 						int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable); | 						int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); | ||||||
| 						if (stackDiff < 1 || stackDiff > 16) | 						if (stackDiff < 1 || stackDiff > 16) | ||||||
| 							BOOST_THROW_EXCEPTION( | 							BOOST_THROW_EXCEPTION( | ||||||
| 								CompilerError() << | 								CompilerError() << | ||||||
| @ -565,7 +561,7 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly) | |||||||
| 					"Can only assign to stack variables in inline assembly." | 					"Can only assign to stack variables in inline assembly." | ||||||
| 				); | 				); | ||||||
| 				unsigned size = variable->type()->sizeOnStack(); | 				unsigned size = variable->type()->sizeOnStack(); | ||||||
| 				int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable) - size; | 				int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size; | ||||||
| 				if (stackDiff > 16 || stackDiff < 1) | 				if (stackDiff > 16 || stackDiff < 1) | ||||||
| 					BOOST_THROW_EXCEPTION( | 					BOOST_THROW_EXCEPTION( | ||||||
| 						CompilerError() << | 						CompilerError() << | ||||||
| @ -578,8 +574,9 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly) | |||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 	)); | 	); | ||||||
| 	solAssert(errors.empty(), "Code generation for inline assembly with errors requested."); | 	solAssert(errors.empty(), "Code generation for inline assembly with errors requested."); | ||||||
|  | 	m_context.setStackOffset(startStackHeight); | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -113,8 +113,6 @@ public: | |||||||
| 	/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
 | 	/// 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.
 | 	/// 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); } | 	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
 | 	/// Pushes the size of the final program
 | ||||||
| 	void appendProgramSize() { return m_asm.appendProgramSize(); } | 	void appendProgramSize() { return m_asm.appendProgramSize(); } | ||||||
| 	/// Adds data to the data section, pushes a reference to the stack
 | 	/// Adds data to the data section, pushes a reference to the stack
 | ||||||
| @ -140,6 +138,10 @@ public: | |||||||
| 	void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } | 	void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } | ||||||
| 
 | 
 | ||||||
| 	eth::Assembly const& assembly() const { return m_asm; } | 	eth::Assembly const& assembly() const { return m_asm; } | ||||||
|  | 	/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
 | ||||||
|  | 	/// wrappers in this class.
 | ||||||
|  | 	eth::Assembly& nonConstAssembly() { return m_asm; } | ||||||
|  | 
 | ||||||
| 	/// @arg _sourceCodes is the map of input files to source code strings
 | 	/// @arg _sourceCodes is the map of input files to source code strings
 | ||||||
| 	/// @arg _inJsonFormat shows whether the out should be in Json format
 | 	/// @arg _inJsonFormat shows whether the out should be in Json format
 | ||||||
| 	Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const | 	Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const | ||||||
|  | |||||||
| @ -36,7 +36,8 @@ using namespace dev::solidity::assembly; | |||||||
| 
 | 
 | ||||||
| struct GeneratorState | struct GeneratorState | ||||||
| { | { | ||||||
| 	explicit GeneratorState(ErrorList& _errors): errors(_errors) {} | 	GeneratorState(ErrorList& _errors, eth::Assembly& _assembly): | ||||||
|  | 		errors(_errors), assembly(_assembly) {} | ||||||
| 
 | 
 | ||||||
| 	void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) | 	void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) | ||||||
| 	{ | 	{ | ||||||
| @ -66,10 +67,10 @@ struct GeneratorState | |||||||
| 		return label != labels.end() ? &label->second : nullptr; | 		return label != labels.end() ? &label->second : nullptr; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	eth::Assembly assembly; |  | ||||||
| 	map<string, eth::AssemblyItem> labels; | 	map<string, eth::AssemblyItem> labels; | ||||||
| 	vector<pair<string, int>> variables; ///< name plus stack height
 | 	vector<pair<string, int>> variables; ///< name plus stack height
 | ||||||
| 	ErrorList& errors; | 	ErrorList& errors; | ||||||
|  | 	eth::Assembly& assembly; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
| @ -267,7 +268,8 @@ private: | |||||||
| bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||||
| { | { | ||||||
| 	size_t initialErrorLen = m_errors.size(); | 	size_t initialErrorLen = m_errors.size(); | ||||||
| 	GeneratorState state(m_errors); | 	eth::Assembly assembly; | ||||||
|  | 	GeneratorState state(m_errors, assembly); | ||||||
| 	(LabelOrganizer(state))(m_parsedData); | 	(LabelOrganizer(state))(m_parsedData); | ||||||
| 	(CodeTransform(state, _identifierAccess))(m_parsedData); | 	(CodeTransform(state, _identifierAccess))(m_parsedData); | ||||||
| 	return m_errors.size() == initialErrorLen; | 	return m_errors.size() == initialErrorLen; | ||||||
| @ -275,9 +277,17 @@ bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAcces | |||||||
| 
 | 
 | ||||||
| eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||||
| { | { | ||||||
| 	GeneratorState state(m_errors); | 	eth::Assembly assembly; | ||||||
|  | 	GeneratorState state(m_errors, assembly); | ||||||
|  | 	(LabelOrganizer(state))(m_parsedData); | ||||||
|  | 	(CodeTransform(state, _identifierAccess))(m_parsedData); | ||||||
|  | 	return assembly; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||||
|  | { | ||||||
|  | 	GeneratorState state(m_errors, _assembly); | ||||||
| 	(LabelOrganizer(state))(m_parsedData); | 	(LabelOrganizer(state))(m_parsedData); | ||||||
| 	(CodeTransform(state, _identifierAccess))(m_parsedData); | 	(CodeTransform(state, _identifierAccess))(m_parsedData); | ||||||
| 	return state.assembly; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -55,6 +55,8 @@ public: | |||||||
| 	bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | 	bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||||
| 	/// Performs code generation and @returns the result.
 | 	/// Performs code generation and @returns the result.
 | ||||||
| 	eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | 	eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||||
|  | 	/// Performs code generation and appends generated to to _assembly.
 | ||||||
|  | 	void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	Block const& m_parsedData; | 	Block const& m_parsedData; | ||||||
|  | |||||||
| @ -6595,6 +6595,25 @@ BOOST_AUTO_TEST_CASE(inline_assembly_jumps) | |||||||
| 	BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34))); | 	BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(inline_assembly_function_access) | ||||||
|  | { | ||||||
|  | 	char const* sourceCode = R"( | ||||||
|  | 		contract C { | ||||||
|  | 			uint public x; | ||||||
|  | 			function g(uint y) { x = 2 * y; assembly { stop } } | ||||||
|  | 			function f(uint _x) { | ||||||
|  | 				assembly { | ||||||
|  | 					_x | ||||||
|  | 					jump(g) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	)"; | ||||||
|  | 	compileAndRun(sourceCode, 0, "C"); | ||||||
|  | 	BOOST_CHECK(callContractFunction("f(uint256)", u256(5)) == encodeArgs()); | ||||||
|  | 	BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10))); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| BOOST_AUTO_TEST_CASE(index_access_with_type_conversion) | 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.
 | 	// Test for a bug where higher order bits cleanup was not done for array index access.
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user