mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #8583 from ethereum/yulForImmutables
Yul codegen for immutables.
This commit is contained in:
		
						commit
						ace1eacb88
					
				
							
								
								
									
										33
									
								
								docs/yul.rst
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								docs/yul.rst
									
									
									
									
									
								
							| @ -885,13 +885,6 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a | ||||
| | gaslimit()              |     | F | block gas limit of the current block                            | | ||||
| +-------------------------+-----+---+-----------------------------------------------------------------+ | ||||
| 
 | ||||
| There are three additional functions, ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, | ||||
| which are used to access other parts of a Yul object. | ||||
| 
 | ||||
| ``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) | ||||
| as arguments and return the size and offset in the data area, respectively. | ||||
| For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. | ||||
| 
 | ||||
| .. _yul-call-return-area: | ||||
| 
 | ||||
| .. note:: | ||||
| @ -903,6 +896,32 @@ For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. | ||||
|   The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``), | ||||
|   nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``. | ||||
| 
 | ||||
| 
 | ||||
| In some internal dialects, there are additional functions: | ||||
| 
 | ||||
| datasize, dataoffset, datacopy | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| 
 | ||||
| The functions ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, | ||||
| are used to access other parts of a Yul object. | ||||
| 
 | ||||
| ``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) | ||||
| as arguments and return the size and offset in the data area, respectively. | ||||
| For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. | ||||
| 
 | ||||
| 
 | ||||
| setimmutable, loadimmutable | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| 
 | ||||
| The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are | ||||
| used for the immutable mechanism in Solidity and do not nicely map to pur Yul. | ||||
| The function ``setimmutable`` assumes that the runtime code of a contract | ||||
| is currently copied to memory at offsot zero. The call to ``setimmutable("name", value)`` | ||||
| will store ``value`` at all points in memory that contain a call to | ||||
| ``loadimmutable("name")``. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| .. _yul-object: | ||||
| 
 | ||||
| Specification of Yul Object | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| 
 | ||||
| #include <libsolidity/codegen/YulUtilFunctions.h> | ||||
| #include <libsolidity/codegen/ABIFunctions.h> | ||||
| #include <libsolidity/codegen/CompilerUtils.h> | ||||
| #include <libsolidity/ast/AST.h> | ||||
| #include <libsolidity/ast/TypeProvider.h> | ||||
| 
 | ||||
| @ -76,6 +77,36 @@ IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const& | ||||
| 	return m_localVariables.at(&_varDecl); | ||||
| } | ||||
| 
 | ||||
| void IRGenerationContext::registerImmutableVariable(VariableDeclaration const& _variable) | ||||
| { | ||||
| 	solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable."); | ||||
| 	solUnimplementedAssert( | ||||
| 		_variable.annotation().type->isValueType(), | ||||
| 		"Only immutable variables of value type are supported." | ||||
| 	); | ||||
| 	solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); | ||||
| 	m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; | ||||
| 	solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap."); | ||||
| 	*m_reservedMemory += _variable.annotation().type->memoryHeadSize(); | ||||
| } | ||||
| 
 | ||||
| size_t IRGenerationContext::immutableMemoryOffset(VariableDeclaration const& _variable) const | ||||
| { | ||||
| 	solAssert( | ||||
| 		m_immutableVariables.count(&_variable), | ||||
| 		"Unknown immutable variable: " + _variable.name() | ||||
| 	); | ||||
| 	return m_immutableVariables.at(&_variable); | ||||
| } | ||||
| 
 | ||||
| size_t IRGenerationContext::reservedMemory() | ||||
| { | ||||
| 	solAssert(m_reservedMemory.has_value(), "Reserved memory was used before."); | ||||
| 	size_t reservedMemory = *m_reservedMemory; | ||||
| 	m_reservedMemory = std::nullopt; | ||||
| 	return reservedMemory; | ||||
| } | ||||
| 
 | ||||
| void IRGenerationContext::addStateVariable( | ||||
| 	VariableDeclaration const& _declaration, | ||||
| 	u256 _storageOffset, | ||||
|  | ||||
| @ -81,6 +81,17 @@ public: | ||||
| 	bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } | ||||
| 	IRVariable const& localVariable(VariableDeclaration const& _varDecl); | ||||
| 
 | ||||
| 	/// Registers an immutable variable of the contract.
 | ||||
| 	/// Should only be called at construction time.
 | ||||
| 	void registerImmutableVariable(VariableDeclaration const& _varDecl); | ||||
| 	/// @returns the reserved memory for storing the value of the
 | ||||
| 	/// immutable @a _variable during contract creation.
 | ||||
| 	size_t immutableMemoryOffset(VariableDeclaration const& _variable) const; | ||||
| 	/// @returns the reserved memory and resets it to mark it as used.
 | ||||
| 	/// Intended to be used only once for initializing the free memory pointer
 | ||||
| 	/// to after the area used for immutables.
 | ||||
| 	size_t reservedMemory(); | ||||
| 
 | ||||
| 	void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); | ||||
| 	bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } | ||||
| 	std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const | ||||
| @ -123,6 +134,12 @@ private: | ||||
| 	OptimiserSettings m_optimiserSettings; | ||||
| 	ContractDefinition const* m_mostDerivedContract = nullptr; | ||||
| 	std::map<VariableDeclaration const*, IRVariable> m_localVariables; | ||||
| 	/// Memory offsets reserved for the values of immutable variables during contract creation.
 | ||||
| 	/// This map is empty in the runtime context.
 | ||||
| 	std::map<VariableDeclaration const*, size_t> m_immutableVariables; | ||||
| 	/// Total amount of reserved memory. Reserved memory is used to store
 | ||||
| 	/// immutable variables during contract creation.
 | ||||
| 	std::optional<size_t> m_reservedMemory = {0}; | ||||
| 	/// Storage offsets of state variables
 | ||||
| 	std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; | ||||
| 	MultiUseYulFunctionCollector m_functions; | ||||
|  | ||||
| @ -114,6 +114,8 @@ string IRGenerator::generate( | ||||
| 	)"); | ||||
| 
 | ||||
| 	resetContext(_contract); | ||||
| 	for (VariableDeclaration const* var: ContractType(_contract).immutableVariables()) | ||||
| 		m_context.registerImmutableVariable(*var); | ||||
| 
 | ||||
| 	t("CreationObject", m_context.creationObjectName(_contract)); | ||||
| 	t("memoryInit", memoryInit()); | ||||
| @ -142,6 +144,7 @@ string IRGenerator::generate( | ||||
| 	t("subObjects", subObjectSources(m_context.subObjectsCreated())); | ||||
| 
 | ||||
| 	resetContext(_contract); | ||||
| 	// Do not register immutables to avoid assignment.
 | ||||
| 	t("RuntimeObject", m_context.runtimeObjectName(_contract)); | ||||
| 	t("dispatch", dispatchRoutine(_contract)); | ||||
| 	generateQueuedFunctions(); | ||||
| @ -200,7 +203,6 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) | ||||
| 	Type const* type = _varDecl.annotation().type; | ||||
| 
 | ||||
| 	solAssert(!_varDecl.isConstant(), ""); | ||||
| 	solAssert(!_varDecl.immutable(), ""); | ||||
| 	solAssert(_varDecl.isStateVariable(), ""); | ||||
| 
 | ||||
| 	if (auto const* mappingType = dynamic_cast<MappingType const*>(type)) | ||||
| @ -254,17 +256,32 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) | ||||
| 		solUnimplementedAssert(type->isValueType(), ""); | ||||
| 
 | ||||
| 		return m_context.functionCollector().createFunction(functionName, [&]() { | ||||
| 			pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); | ||||
| 			if (_varDecl.immutable()) | ||||
| 			{ | ||||
| 				solUnimplementedAssert(type->sizeOnStack() == 1, ""); | ||||
| 				return Whiskers(R"( | ||||
| 					function <functionName>() -> rval { | ||||
| 						rval := loadimmutable("<id>") | ||||
| 					} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				("id", to_string(_varDecl.id())) | ||||
| 				.render(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); | ||||
| 
 | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>() -> rval { | ||||
| 					rval := <readStorage>(<slot>) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) | ||||
| 			("slot", slot_offset.first.str()) | ||||
| 			.render(); | ||||
| 				return Whiskers(R"( | ||||
| 					function <functionName>() -> rval { | ||||
| 						rval := <readStorage>(<slot>) | ||||
| 					} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) | ||||
| 				("slot", slot_offset.first.str()) | ||||
| 				.render(); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| @ -325,7 +342,7 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract) | ||||
| { | ||||
| 	IRGeneratorForStatements generator{m_context, m_utils}; | ||||
| 	for (VariableDeclaration const* variable: _contract.stateVariables()) | ||||
| 		if (!variable->isConstant() && !variable->immutable()) | ||||
| 		if (!variable->isConstant()) | ||||
| 			generator.initializeStateVar(*variable); | ||||
| 
 | ||||
| 	return generator.code(); | ||||
| @ -391,10 +408,41 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra | ||||
| string IRGenerator::deployCode(ContractDefinition const& _contract) | ||||
| { | ||||
| 	Whiskers t(R"X( | ||||
| 		<#loadImmutables> | ||||
| 			let <var> := mload(<memoryOffset>) | ||||
| 		</loadImmutables> | ||||
| 
 | ||||
| 		codecopy(0, dataoffset("<object>"), datasize("<object>")) | ||||
| 
 | ||||
| 		<#storeImmutables> | ||||
| 			setimmutable("<immutableName>", <var>) | ||||
| 		</storeImmutables> | ||||
| 
 | ||||
| 		return(0, datasize("<object>")) | ||||
| 	)X"); | ||||
| 	t("object", m_context.runtimeObjectName(_contract)); | ||||
| 
 | ||||
| 	vector<map<string, string>> loadImmutables; | ||||
| 	vector<map<string, string>> storeImmutables; | ||||
| 
 | ||||
| 	for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) | ||||
| 	{ | ||||
| 		solUnimplementedAssert(immutable->type()->isValueType(), ""); | ||||
| 		solUnimplementedAssert(immutable->type()->sizeOnStack() == 1, ""); | ||||
| 		string yulVar = m_context.newYulVariable(); | ||||
| 		loadImmutables.emplace_back(map<string, string>{ | ||||
| 			{"var"s, yulVar}, | ||||
| 			{"memoryOffset"s, to_string(m_context.immutableMemoryOffset(*immutable))} | ||||
| 		}); | ||||
| 		storeImmutables.emplace_back(map<string, string>{ | ||||
| 			{"var"s, yulVar}, | ||||
| 			{"immutableName"s, to_string(immutable->id())} | ||||
| 		}); | ||||
| 	} | ||||
| 	t("loadImmutables", std::move(loadImmutables)); | ||||
| 	// reverse order to ease stack strain
 | ||||
| 	reverse(storeImmutables.begin(), storeImmutables.end()); | ||||
| 	t("storeImmutables", std::move(storeImmutables)); | ||||
| 	return t.render(); | ||||
| } | ||||
| 
 | ||||
| @ -489,9 +537,9 @@ string IRGenerator::memoryInit() | ||||
| 	// and thus can assume all memory to be zero, including the contents of
 | ||||
| 	// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
 | ||||
| 	return | ||||
| 		Whiskers{"mstore(<memPtr>, <generalPurposeStart>)"} | ||||
| 		Whiskers{"mstore(<memPtr>, <freeMemoryStart>)"} | ||||
| 		("memPtr", to_string(CompilerUtils::freeMemoryPointer)) | ||||
| 		("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) | ||||
| 		("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())) | ||||
| 		.render(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -140,20 +140,21 @@ string IRGeneratorForStatements::code() const | ||||
| 
 | ||||
| void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable."); | ||||
| 	solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); | ||||
| 	solAssert(!_varDecl.isConstant(), ""); | ||||
| 	solAssert(!_varDecl.immutable(), ""); | ||||
| 	if (_varDecl.value()) | ||||
| 	{ | ||||
| 		_varDecl.value()->accept(*this); | ||||
| 		writeToLValue(IRLValue{ | ||||
| 			*_varDecl.annotation().type, | ||||
| 			IRLValue::Storage{ | ||||
| 				util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first), | ||||
| 				m_context.storageLocationOfVariable(_varDecl).second | ||||
| 			} | ||||
| 		}, *_varDecl.value()); | ||||
| 	} | ||||
| 	if (!_varDecl.value()) | ||||
| 		return; | ||||
| 
 | ||||
| 	_varDecl.value()->accept(*this); | ||||
| 	writeToLValue( | ||||
| 		_varDecl.immutable() ? | ||||
| 		IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} : | ||||
| 		IRLValue{*_varDecl.annotation().type, IRLValue::Storage{ | ||||
| 			util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first), | ||||
| 			m_context.storageLocationOfVariable(_varDecl).second | ||||
| 		}}, | ||||
| 		*_varDecl.value() | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl) | ||||
| @ -1517,8 +1518,12 @@ void IRGeneratorForStatements::handleVariableReference( | ||||
| 	// If the value is visited twice, `defineExpression` is called twice on
 | ||||
| 	// the same expression.
 | ||||
| 	solUnimplementedAssert(!_variable.isConstant(), ""); | ||||
| 	solUnimplementedAssert(!_variable.immutable(), ""); | ||||
| 	if (m_context.isLocalVariable(_variable)) | ||||
| 	if (_variable.isStateVariable() && _variable.immutable()) | ||||
| 		setLValue(_referencingExpression, IRLValue{ | ||||
| 			*_variable.annotation().type, | ||||
| 			IRLValue::Immutable{&_variable} | ||||
| 		}); | ||||
| 	else if (m_context.isLocalVariable(_variable)) | ||||
| 		setLValue(_referencingExpression, IRLValue{ | ||||
| 			*_variable.annotation().type, | ||||
| 			IRLValue::Stack{m_context.localVariable(_variable)} | ||||
| @ -1939,6 +1944,18 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable | ||||
| 				} | ||||
| 			}, | ||||
| 			[&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); }, | ||||
| 			[&](IRLValue::Immutable const& _immutable) | ||||
| 			{ | ||||
| 				solUnimplementedAssert(_lvalue.type.isValueType(), ""); | ||||
| 				solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, ""); | ||||
| 				solAssert(_lvalue.type == *_immutable.variable->type(), ""); | ||||
| 				size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable); | ||||
| 
 | ||||
| 				IRVariable prepared(m_context.newYulVariable(), _lvalue.type); | ||||
| 				define(prepared, _value); | ||||
| 
 | ||||
| 				m_code << "mstore(" << to_string(memOffset) << ", " << prepared.commaSeparatedList() << ")\n"; | ||||
| 			}, | ||||
| 			[&](IRLValue::Tuple const& _tuple) { | ||||
| 				auto components = std::move(_tuple.components); | ||||
| 				for (size_t i = 0; i < components.size(); i++) | ||||
| @ -1994,6 +2011,12 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) | ||||
| 		[&](IRLValue::Stack const& _stack) { | ||||
| 			define(result, _stack.variable); | ||||
| 		}, | ||||
| 		[&](IRLValue::Immutable const& _immutable) { | ||||
| 			solUnimplementedAssert(_lvalue.type.isValueType(), ""); | ||||
| 			solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, ""); | ||||
| 			solAssert(_lvalue.type == *_immutable.variable->type(), ""); | ||||
| 			define(result) << "loadimmutable(\"" << to_string(_immutable.variable->id()) << "\")\n"; | ||||
| 		}, | ||||
| 		[&](IRLValue::Tuple const&) { | ||||
| 			solAssert(false, "Attempted to read from tuple lvalue."); | ||||
| 		} | ||||
|  | ||||
| @ -35,6 +35,10 @@ struct IRLValue | ||||
| 	{ | ||||
| 		IRVariable variable; | ||||
| 	}; | ||||
| 	struct Immutable | ||||
| 	{ | ||||
| 		VariableDeclaration const* variable = nullptr; | ||||
| 	}; | ||||
| 	struct Storage | ||||
| 	{ | ||||
| 		std::string const slot; | ||||
| @ -59,7 +63,7 @@ struct IRLValue | ||||
| 	{ | ||||
| 		std::vector<std::optional<IRLValue>> components; | ||||
| 	}; | ||||
| 	std::variant<Stack, Storage, Memory, Tuple> kind; | ||||
| 	std::variant<Stack, Immutable, Storage, Memory, Tuple> kind; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| @ -306,11 +306,15 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall) | ||||
| 					_funCall.functionName.location, | ||||
| 					"Function expects direct literals as arguments." | ||||
| 				); | ||||
| 			else if (!m_dataNames.count(std::get<Literal>(arg).value)) | ||||
| 				typeError( | ||||
| 					_funCall.functionName.location, | ||||
| 					"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"." | ||||
| 				); | ||||
| 			else if ( | ||||
| 				_funCall.functionName.name.str() == "datasize" || | ||||
| 				_funCall.functionName.name.str() == "dataoffset" | ||||
| 			) | ||||
| 				if (!m_dataNames.count(std::get<Literal>(arg).value)) | ||||
| 					typeError( | ||||
| 						_funCall.functionName.location, | ||||
| 						"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"." | ||||
| 					); | ||||
| 		} | ||||
| 	} | ||||
| 	std::reverse(argTypes.begin(), argTypes.end()); | ||||
|  | ||||
| @ -105,6 +105,11 @@ public: | ||||
| 	virtual void appendDataSize(SubID _sub) = 0; | ||||
| 	/// Appends the given data to the assembly and returns its ID.
 | ||||
| 	virtual SubID appendData(bytes const& _data) = 0; | ||||
| 
 | ||||
| 	/// Appends loading an immutable variable.
 | ||||
| 	virtual void appendImmutable(std::string const& _identifier) = 0; | ||||
| 	/// Appends an assignment to an immutable variable.
 | ||||
| 	virtual void appendImmutableAssignment(std::string const& _identifier) = 0; | ||||
| }; | ||||
| 
 | ||||
| enum class IdentifierContext { LValue, RValue, VariableDeclaration }; | ||||
|  | ||||
| @ -172,6 +172,16 @@ AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) | ||||
| 	return subID; | ||||
| } | ||||
| 
 | ||||
| void EthAssemblyAdapter::appendImmutable(std::string const& _identifier) | ||||
| { | ||||
| 	m_assembly.appendImmutable(_identifier); | ||||
| } | ||||
| 
 | ||||
| void EthAssemblyAdapter::appendImmutableAssignment(std::string const& _identifier) | ||||
| { | ||||
| 	m_assembly.appendImmutableAssignment(_identifier); | ||||
| } | ||||
| 
 | ||||
| EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag) | ||||
| { | ||||
| 	u256 id = _tag.data(); | ||||
|  | ||||
| @ -61,6 +61,9 @@ public: | ||||
| 	void appendDataSize(SubID _sub) override; | ||||
| 	SubID appendData(bytes const& _data) override; | ||||
| 
 | ||||
| 	void appendImmutable(std::string const& _identifier) override; | ||||
| 	void appendImmutableAssignment(std::string const& _identifier) override; | ||||
| 
 | ||||
| private: | ||||
| 	static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag); | ||||
| 
 | ||||
|  | ||||
| @ -215,6 +215,16 @@ AbstractAssembly::SubID EVMAssembly::appendData(bytes const&) | ||||
| 	yulAssert(false, "Data not implemented."); | ||||
| } | ||||
| 
 | ||||
| void EVMAssembly::appendImmutable(std::string const&) | ||||
| { | ||||
| 	yulAssert(false, "loadimmutable not implemented."); | ||||
| } | ||||
| 
 | ||||
| void EVMAssembly::appendImmutableAssignment(std::string const&) | ||||
| { | ||||
| 	yulAssert(false, "setimmutable not implemented."); | ||||
| } | ||||
| 
 | ||||
| void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) | ||||
| { | ||||
| 	yulAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); | ||||
|  | ||||
| @ -83,6 +83,9 @@ public: | ||||
| 	void appendDataSize(SubID _sub) override; | ||||
| 	SubID appendData(bytes const& _data) override; | ||||
| 
 | ||||
| 	void appendImmutable(std::string const& _identifier) override; | ||||
| 	void appendImmutableAssignment(std::string const& _identifier) override; | ||||
| 
 | ||||
| 	/// Resolves references inside the bytecode and returns the linker object.
 | ||||
| 	evmasm::LinkerObject finalize(); | ||||
| 
 | ||||
|  | ||||
| @ -257,13 +257,9 @@ void CodeTransform::operator()(FunctionCall const& _call) | ||||
| 	yulAssert(m_scope, ""); | ||||
| 
 | ||||
| 	if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name)) | ||||
| 	{ | ||||
| 		builtin->generateCode(_call, m_assembly, m_builtinContext, [&]() { | ||||
| 			for (auto const& arg: _call.arguments | boost::adaptors::reversed) | ||||
| 				visitExpression(arg); | ||||
| 			m_assembly.setSourceLocation(_call.location); | ||||
| 		builtin->generateCode(_call, m_assembly, m_builtinContext, [&](Expression const& _expression) { | ||||
| 			visitExpression(_expression); | ||||
| 		}); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		m_assembly.setSourceLocation(_call.location); | ||||
|  | ||||
| @ -41,6 +41,20 @@ using namespace solidity::util; | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| 
 | ||||
| void visitArguments( | ||||
| 	AbstractAssembly& _assembly, | ||||
| 	FunctionCall const& _call, | ||||
| 	function<void(Expression const&)> _visitExpression | ||||
| ) | ||||
| { | ||||
| 	for (auto const& arg: _call.arguments | boost::adaptors::reversed) | ||||
| 		_visitExpression(arg); | ||||
| 
 | ||||
| 	_assembly.setSourceLocation(_call.location); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| pair<YulString, BuiltinFunctionForEVM> createEVMFunction( | ||||
| 	string const& _name, | ||||
| 	evmasm::Instruction _instruction | ||||
| @ -58,12 +72,12 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction( | ||||
| 	f.literalArguments.reset(); | ||||
| 	f.instruction = _instruction; | ||||
| 	f.generateCode = [_instruction]( | ||||
| 		FunctionCall const&, | ||||
| 		FunctionCall const& _call, | ||||
| 		AbstractAssembly& _assembly, | ||||
| 		BuiltinContext&, | ||||
| 		std::function<void()> _visitArguments | ||||
| 		std::function<void(Expression const&)> _visitExpression | ||||
| 	) { | ||||
| 		_visitArguments(); | ||||
| 		visitArguments(_assembly, _call, _visitExpression); | ||||
| 		_assembly.appendInstruction(_instruction); | ||||
| 	}; | ||||
| 
 | ||||
| @ -76,7 +90,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction( | ||||
| 	size_t _returns, | ||||
| 	SideEffects _sideEffects, | ||||
| 	vector<bool> _literalArguments, | ||||
| 	std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode | ||||
| 	std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> _generateCode | ||||
| ) | ||||
| { | ||||
| 	solAssert(_literalArguments.size() == _params || _literalArguments.empty(), ""); | ||||
| @ -116,7 +130,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe | ||||
| 			FunctionCall const& _call, | ||||
| 			AbstractAssembly& _assembly, | ||||
| 			BuiltinContext& _context, | ||||
| 			function<void()> | ||||
| 			std::function<void(Expression const&)> | ||||
| 		) { | ||||
| 			yulAssert(_context.currentObject, "No object available."); | ||||
| 			yulAssert(_call.arguments.size() == 1, ""); | ||||
| @ -137,7 +151,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe | ||||
| 			FunctionCall const& _call, | ||||
| 			AbstractAssembly& _assembly, | ||||
| 			BuiltinContext& _context, | ||||
| 			std::function<void()> | ||||
| 			std::function<void(Expression const&)> | ||||
| 		) { | ||||
| 			yulAssert(_context.currentObject, "No object available."); | ||||
| 			yulAssert(_call.arguments.size() == 1, ""); | ||||
| @ -161,21 +175,58 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe | ||||
| 			SideEffects{false, false, false, false, true}, | ||||
| 			{}, | ||||
| 			[]( | ||||
| 				FunctionCall const&, | ||||
| 				FunctionCall const& _call, | ||||
| 				AbstractAssembly& _assembly, | ||||
| 				BuiltinContext&, | ||||
| 				std::function<void()> _visitArguments | ||||
| 				std::function<void(Expression const&)> _visitExpression | ||||
| 			) { | ||||
| 				_visitArguments(); | ||||
| 				visitArguments(_assembly, _call, _visitExpression); | ||||
| 				_assembly.appendInstruction(evmasm::Instruction::CODECOPY); | ||||
| 			} | ||||
| 		)); | ||||
| 		builtins.emplace(createFunction( | ||||
| 			"setimmutable", | ||||
| 			2, | ||||
| 			0, | ||||
| 			SideEffects{false, false, false, false, true}, | ||||
| 			{true, false}, | ||||
| 			[]( | ||||
| 				FunctionCall const& _call, | ||||
| 				AbstractAssembly& _assembly, | ||||
| 				BuiltinContext&, | ||||
| 				std::function<void(Expression const&)> _visitExpression | ||||
| 			) { | ||||
| 				solAssert(_call.arguments.size() == 2, ""); | ||||
| 
 | ||||
| 				_visitExpression(_call.arguments[1]); | ||||
| 				_assembly.setSourceLocation(_call.location); | ||||
| 				YulString identifier = std::get<Literal>(_call.arguments.front()).value; | ||||
| 				_assembly.appendImmutableAssignment(identifier.str()); | ||||
| 			} | ||||
| 		)); | ||||
| 		builtins.emplace(createFunction( | ||||
| 			"loadimmutable", | ||||
| 			1, | ||||
| 			1, | ||||
| 			SideEffects{}, | ||||
| 			{true}, | ||||
| 			[]( | ||||
| 				FunctionCall const& _call, | ||||
| 				AbstractAssembly& _assembly, | ||||
| 				BuiltinContext&, | ||||
| 				std::function<void(Expression const&)> | ||||
| 			) { | ||||
| 				solAssert(_call.arguments.size() == 1, ""); | ||||
| 				_assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str()); | ||||
| 			} | ||||
| 		)); | ||||
| 	} | ||||
| 	return builtins; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess): | ||||
| 	m_objectAccess(_objectAccess), | ||||
| 	m_evmVersion(_evmVersion), | ||||
| @ -268,23 +319,23 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA | ||||
| 	m_functions["popbool"_yulstring].name = "popbool"_yulstring; | ||||
| 	m_functions["popbool"_yulstring].parameters = {"bool"_yulstring}; | ||||
| 	m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, {}, []( | ||||
| 		FunctionCall const&, | ||||
| 		AbstractAssembly&, | ||||
| 		FunctionCall const& _call, | ||||
| 		AbstractAssembly& _assembly, | ||||
| 		BuiltinContext&, | ||||
| 		std::function<void()> _visitArguments | ||||
| 		std::function<void(Expression const&)> _visitExpression | ||||
| 	) { | ||||
| 		_visitArguments(); | ||||
| 		visitArguments(_assembly, _call, _visitExpression); | ||||
| 	})); | ||||
| 	m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring}; | ||||
| 	m_functions["bool_to_u256"_yulstring].returns = {"u256"_yulstring}; | ||||
| 	m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, {}, []( | ||||
| 		FunctionCall const&, | ||||
| 		FunctionCall const& _call, | ||||
| 		AbstractAssembly& _assembly, | ||||
| 		BuiltinContext&, | ||||
| 		std::function<void()> _visitArguments | ||||
| 		std::function<void(Expression const&)> _visitExpression | ||||
| 	) { | ||||
| 		// A value larger than 1 causes an invalid instruction.
 | ||||
| 		_visitArguments(); | ||||
| 		visitArguments(_assembly, _call, _visitExpression); | ||||
| 		_assembly.appendConstant(2); | ||||
| 		_assembly.appendInstruction(evmasm::Instruction::DUP2); | ||||
| 		_assembly.appendInstruction(evmasm::Instruction::LT); | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
| #include <libyul/Dialect.h> | ||||
| 
 | ||||
| #include <libyul/backends/evm/AbstractAssembly.h> | ||||
| #include <libyul/AsmData.h> | ||||
| #include <liblangutil/EVMVersion.h> | ||||
| 
 | ||||
| #include <map> | ||||
| @ -49,9 +50,9 @@ struct BuiltinFunctionForEVM: public BuiltinFunction | ||||
| { | ||||
| 	std::optional<evmasm::Instruction> instruction; | ||||
| 	/// Function to generate code for the given function call and append it to the abstract
 | ||||
| 	/// assembly. The fourth parameter is called to visit (and generate code for) the arguments
 | ||||
| 	/// from right to left.
 | ||||
| 	std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> generateCode; | ||||
| 	/// assembly. The fourth parameter is called to visit (and generate code for) the given
 | ||||
| 	/// argument.
 | ||||
| 	std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> generateCode; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -19,10 +19,14 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include <libyul/backends/evm/NoOutputAssembly.h> | ||||
| 
 | ||||
| #include <libyul/Exceptions.h> | ||||
| 
 | ||||
| #include <libevmasm/Instruction.h> | ||||
| 
 | ||||
| #include <boost/range/adaptor/reversed.hpp> | ||||
| 
 | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace solidity; | ||||
| using namespace solidity::yul; | ||||
| @ -142,6 +146,17 @@ AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&) | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void NoOutputAssembly::appendImmutable(std::string const&) | ||||
| { | ||||
| 	yulAssert(false, "loadimmutable not implemented."); | ||||
| } | ||||
| 
 | ||||
| void NoOutputAssembly::appendImmutableAssignment(std::string const&) | ||||
| { | ||||
| 	yulAssert(false, "setimmutable not implemented."); | ||||
| } | ||||
| 
 | ||||
| NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): | ||||
| 	EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess()) | ||||
| { | ||||
| @ -149,9 +164,11 @@ NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): | ||||
| 	{ | ||||
| 		size_t parameters = fun.second.parameters.size(); | ||||
| 		size_t returns = fun.second.returns.size(); | ||||
| 		fun.second.generateCode = [=](FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, std::function<void()> _visitArguments) | ||||
| 		fun.second.generateCode = [=](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, std::function<void(Expression const&)> _visitExpression) | ||||
| 		{ | ||||
| 			_visitArguments(); | ||||
| 			for (auto const& arg: _call.arguments | boost::adaptors::reversed) | ||||
| 				_visitExpression(arg); | ||||
| 
 | ||||
| 			for (size_t i = 0; i < parameters; i++) | ||||
| 				_assembly.appendInstruction(evmasm::Instruction::POP); | ||||
| 
 | ||||
|  | ||||
| @ -71,6 +71,9 @@ public: | ||||
| 	void appendDataSize(SubID _sub) override; | ||||
| 	SubID appendData(bytes const& _data) override; | ||||
| 
 | ||||
| 	void appendImmutable(std::string const& _identifier) override; | ||||
| 	void appendImmutableAssignment(std::string const& _identifier) override; | ||||
| 
 | ||||
| private: | ||||
| 	bool m_evm15 = false; ///< if true, switch to evm1.5 mode
 | ||||
| 	int m_stackHeight = 0; | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_6\" { | ||||
|         constructor_C_6() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_6_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_6() { | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_10\" { | ||||
|         constructor_C_10() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_10() { | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_10\" { | ||||
|         constructor_C_10() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_10() { | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_10\" { | ||||
|         constructor_C_10() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_10() { | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_10\" { | ||||
|         constructor_C_10() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_10() { | ||||
|  | ||||
| @ -14,6 +14,7 @@ object \"C_10\" { | ||||
|         constructor_C_10() | ||||
| 
 | ||||
|         codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         return(0, datasize(\"C_10_deployed\")) | ||||
| 
 | ||||
|         function constructor_C_10() { | ||||
|  | ||||
| @ -4,5 +4,7 @@ contract A { | ||||
| 		return a; | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 2 | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| contract C { | ||||
|     uint immutable public x = 1; | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // x() -> 1 | ||||
|  | ||||
| @ -26,5 +26,7 @@ contract D is B, C { | ||||
| 		return (a, b, c, d); | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 4, 3, 2, 1 | ||||
|  | ||||
| @ -10,6 +10,8 @@ contract C { | ||||
| 		return z(); | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 7 | ||||
| // callZ() -> 7 | ||||
|  | ||||
| @ -9,5 +9,7 @@ contract C { | ||||
| 		return (x+x,y); | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 84, 23 | ||||
|  | ||||
| @ -13,6 +13,8 @@ contract C { | ||||
| 		return (x+x,y); | ||||
| 	} | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // constructor(): 3 -> | ||||
| // f() -> 84, 23 | ||||
|  | ||||
							
								
								
									
										11
									
								
								test/libsolidity/syntaxTests/inlineAssembly/immutables.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/libsolidity/syntaxTests/inlineAssembly/immutables.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| contract C { | ||||
|   function f() public pure { | ||||
|     assembly { | ||||
|       setimmutable("abc", 0) | ||||
|       loadimmutable("abc") | ||||
|     } | ||||
|   } | ||||
| } | ||||
| // ---- | ||||
| // DeclarationError: (63-75): Function not found. | ||||
| // DeclarationError: (92-105): Function not found. | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user