mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #8472 from ethereum/immutableCodegen
Immutable codegen
This commit is contained in:
		
						commit
						0029b8bbbf
					
				| @ -283,6 +283,24 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) | ||||
| 				createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end) | ||||
| 			); | ||||
| 			break; | ||||
| 		case PushImmutable: | ||||
| 			collection.append(createJsonValue( | ||||
| 				"PUSHIMMUTABLE", | ||||
| 				sourceIndex, | ||||
| 				i.location().start, | ||||
| 				i.location().end, | ||||
| 				m_immutables.at(h256(i.data())) | ||||
| 			)); | ||||
| 			break; | ||||
| 		case AssignImmutable: | ||||
| 			collection.append(createJsonValue( | ||||
| 				"ASSIGNIMMUTABLE", | ||||
| 				sourceIndex, | ||||
| 				i.location().start, | ||||
| 				i.location().end, | ||||
| 				m_immutables.at(h256(i.data())) | ||||
| 			)); | ||||
| 			break; | ||||
| 		case Tag: | ||||
| 			collection.append( | ||||
| 				createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data()))); | ||||
| @ -333,6 +351,20 @@ AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier) | ||||
| 	return AssemblyItem{PushLibraryAddress, h}; | ||||
| } | ||||
| 
 | ||||
| AssemblyItem Assembly::newPushImmutable(string const& _identifier) | ||||
| { | ||||
| 	h256 h(util::keccak256(_identifier)); | ||||
| 	m_immutables[h] = _identifier; | ||||
| 	return AssemblyItem{PushImmutable, h}; | ||||
| } | ||||
| 
 | ||||
| AssemblyItem Assembly::newImmutableAssignment(string const& _identifier) | ||||
| { | ||||
| 	h256 h(util::keccak256(_identifier)); | ||||
| 	m_immutables[h] = _identifier; | ||||
| 	return AssemblyItem{AssignImmutable, h}; | ||||
| } | ||||
| 
 | ||||
| Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs) | ||||
| { | ||||
| 	OptimiserSettings settings; | ||||
| @ -495,16 +527,44 @@ LinkerObject const& Assembly::assemble() const | ||||
| 	// Otherwise ensure the object is actually clear.
 | ||||
| 	assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references."); | ||||
| 
 | ||||
| 	LinkerObject& ret = m_assembledObject; | ||||
| 
 | ||||
| 	size_t subTagSize = 1; | ||||
| 	map<u256, vector<size_t>> immutableReferencesBySub; | ||||
| 	for (auto const& sub: m_subs) | ||||
| 	{ | ||||
| 		sub->assemble(); | ||||
| 		auto const& linkerObject = sub->assemble(); | ||||
| 		if (!linkerObject.immutableReferences.empty()) | ||||
| 		{ | ||||
| 			assertThrow( | ||||
| 				immutableReferencesBySub.empty(), | ||||
| 				AssemblyException, | ||||
| 				"More than one sub-assembly references immutables." | ||||
| 			); | ||||
| 			immutableReferencesBySub = linkerObject.immutableReferences; | ||||
| 		} | ||||
| 		for (size_t tagPos: sub->m_tagPositionsInBytecode) | ||||
| 			if (tagPos != size_t(-1) && tagPos > subTagSize) | ||||
| 				subTagSize = tagPos; | ||||
| 	} | ||||
| 
 | ||||
| 	LinkerObject& ret = m_assembledObject; | ||||
| 	bool setsImmutables = false; | ||||
| 	bool pushesImmutables = false; | ||||
| 
 | ||||
| 	for (auto const& i: m_items) | ||||
| 		if (i.type() == AssignImmutable) | ||||
| 		{ | ||||
| 			i.setImmutableOccurrences(immutableReferencesBySub[i.data()].size()); | ||||
| 			setsImmutables = true; | ||||
| 		} | ||||
| 		else if (i.type() == PushImmutable) | ||||
| 			pushesImmutables = true; | ||||
| 	if (setsImmutables || pushesImmutables) | ||||
| 		assertThrow( | ||||
| 			setsImmutables != pushesImmutables, | ||||
| 			AssemblyException, | ||||
| 			"Cannot push and assign immutables in the same assembly subroutine." | ||||
| 		); | ||||
| 
 | ||||
| 	size_t bytesRequiredForCode = bytesRequired(subTagSize); | ||||
| 	m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1); | ||||
| @ -598,6 +658,24 @@ LinkerObject const& Assembly::assemble() const | ||||
| 			ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data()); | ||||
| 			ret.bytecode.resize(ret.bytecode.size() + 20); | ||||
| 			break; | ||||
| 		case PushImmutable: | ||||
| 			ret.bytecode.push_back(uint8_t(Instruction::PUSH32)); | ||||
| 			ret.immutableReferences[i.data()].emplace_back(ret.bytecode.size()); | ||||
| 			ret.bytecode.resize(ret.bytecode.size() + 32); | ||||
| 			break; | ||||
| 		case AssignImmutable: | ||||
| 			for (auto const& offset: immutableReferencesBySub[i.data()]) | ||||
| 			{ | ||||
| 				ret.bytecode.push_back(uint8_t(Instruction::DUP1)); | ||||
| 				// TODO: should we make use of the constant optimizer methods for pushing the offsets?
 | ||||
| 				bytes offsetBytes = toCompactBigEndian(u256(offset)); | ||||
| 				ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size()); | ||||
| 				ret.bytecode += offsetBytes; | ||||
| 				ret.bytecode.push_back(uint8_t(Instruction::MSTORE)); | ||||
| 			} | ||||
| 			immutableReferencesBySub.erase(i.data()); | ||||
| 			ret.bytecode.push_back(uint8_t(Instruction::POP)); | ||||
| 			break; | ||||
| 		case PushDeployTimeAddress: | ||||
| 			ret.bytecode.push_back(uint8_t(Instruction::PUSH20)); | ||||
| 			ret.bytecode.resize(ret.bytecode.size() + 20); | ||||
| @ -615,6 +693,13 @@ LinkerObject const& Assembly::assemble() const | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	assertThrow( | ||||
| 		immutableReferencesBySub.empty(), | ||||
| 		AssemblyException, | ||||
| 		"Some immutables were read from but never assigned." | ||||
| 	); | ||||
| 
 | ||||
| 
 | ||||
| 	if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty()) | ||||
| 		// Append an INVALID here to help tests find miscompilation.
 | ||||
| 		ret.bytecode.push_back(uint8_t(Instruction::INVALID)); | ||||
|  | ||||
| @ -54,6 +54,8 @@ public: | ||||
| 	Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } | ||||
| 	AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } | ||||
| 	AssemblyItem newPushLibraryAddress(std::string const& _identifier); | ||||
| 	AssemblyItem newPushImmutable(std::string const& _identifier); | ||||
| 	AssemblyItem newImmutableAssignment(std::string const& _identifier); | ||||
| 
 | ||||
| 	AssemblyItem const& append(AssemblyItem const& _i); | ||||
| 	AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } | ||||
| @ -64,6 +66,8 @@ public: | ||||
| 	/// after compilation and CODESIZE is not an option.
 | ||||
| 	void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } | ||||
| 	void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } | ||||
| 	void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); } | ||||
| 	void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); } | ||||
| 
 | ||||
| 	AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } | ||||
| 	AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } | ||||
| @ -166,6 +170,7 @@ protected: | ||||
| 	std::vector<std::shared_ptr<Assembly>> m_subs; | ||||
| 	std::map<util::h256, std::string> m_strings; | ||||
| 	std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
 | ||||
| 	std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
 | ||||
| 
 | ||||
| 	mutable LinkerObject m_assembledObject; | ||||
| 	mutable std::vector<size_t> m_tagPositionsInBytecode; | ||||
|  | ||||
| @ -80,6 +80,13 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const | ||||
| 	case PushLibraryAddress: | ||||
| 	case PushDeployTimeAddress: | ||||
| 		return 1 + 20; | ||||
| 	case PushImmutable: | ||||
| 		return 1 + 32; | ||||
| 	case AssignImmutable: | ||||
| 		if (m_immutableOccurrences) | ||||
| 			return 1 + (3 + 32) * *m_immutableOccurrences; | ||||
| 		else | ||||
| 			return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
 | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| @ -90,6 +97,8 @@ int AssemblyItem::arguments() const | ||||
| { | ||||
| 	if (type() == Operation) | ||||
| 		return instructionInfo(instruction()).args; | ||||
| 	else if (type() == AssignImmutable) | ||||
| 		return 1; | ||||
| 	else | ||||
| 		return 0; | ||||
| } | ||||
| @ -108,6 +117,7 @@ int AssemblyItem::returnValues() const | ||||
| 	case PushSubSize: | ||||
| 	case PushProgramSize: | ||||
| 	case PushLibraryAddress: | ||||
| 	case PushImmutable: | ||||
| 	case PushDeployTimeAddress: | ||||
| 		return 1; | ||||
| 	case Tag: | ||||
| @ -135,6 +145,7 @@ bool AssemblyItem::canBeFunctional() const | ||||
| 	case PushProgramSize: | ||||
| 	case PushLibraryAddress: | ||||
| 	case PushDeployTimeAddress: | ||||
| 	case PushImmutable: | ||||
| 		return true; | ||||
| 	case Tag: | ||||
| 		return false; | ||||
| @ -210,6 +221,12 @@ string AssemblyItem::toAssemblyText() const | ||||
| 	case PushDeployTimeAddress: | ||||
| 		text = string("deployTimeAddress()"); | ||||
| 		break; | ||||
| 	case PushImmutable: | ||||
| 		text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")"; | ||||
| 		break; | ||||
| 	case AssignImmutable: | ||||
| 		text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")"; | ||||
| 		break; | ||||
| 	case UndefinedItem: | ||||
| 		assertThrow(false, AssemblyException, "Invalid assembly item."); | ||||
| 		break; | ||||
| @ -275,6 +292,12 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item) | ||||
| 	case PushDeployTimeAddress: | ||||
| 		_out << " PushDeployTimeAddress"; | ||||
| 		break; | ||||
| 	case PushImmutable: | ||||
| 		_out << " PushImmutable"; | ||||
| 		break; | ||||
| 	case AssignImmutable: | ||||
| 		_out << " AssignImmutable"; | ||||
| 		break; | ||||
| 	case UndefinedItem: | ||||
| 		_out << " ???"; | ||||
| 		break; | ||||
|  | ||||
| @ -44,7 +44,9 @@ enum AssemblyItemType { | ||||
| 	Tag, | ||||
| 	PushData, | ||||
| 	PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
 | ||||
| 	PushDeployTimeAddress ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
 | ||||
| 	PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
 | ||||
| 	PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
 | ||||
| 	AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
 | ||||
| }; | ||||
| 
 | ||||
| class Assembly; | ||||
| @ -153,6 +155,8 @@ public: | ||||
| 
 | ||||
| 	size_t m_modifierDepth = 0; | ||||
| 
 | ||||
| 	void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared<size_t>(_n); } | ||||
| 
 | ||||
| private: | ||||
| 	AssemblyItemType m_type; | ||||
| 	Instruction m_instruction; ///< Only valid if m_type == Operation
 | ||||
| @ -162,6 +166,8 @@ private: | ||||
| 	/// Pushed value for operations with data to be determined during assembly stage,
 | ||||
| 	/// e.g. PushSubSize, PushTag, PushSub, etc.
 | ||||
| 	mutable std::shared_ptr<u256> m_pushedValue; | ||||
| 	/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
 | ||||
| 	mutable std::shared_ptr<size_t> m_immutableOccurrences; | ||||
| }; | ||||
| 
 | ||||
| inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength) | ||||
|  | ||||
| @ -91,6 +91,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool | ||||
| 	{ | ||||
| 		// can be ignored
 | ||||
| 	} | ||||
| 	else if (_item.type() == AssignImmutable) | ||||
| 		// Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which
 | ||||
| 		// is the same as POP.
 | ||||
| 		return feedItem(AssemblyItem(Instruction::POP), _copyItem); | ||||
| 	else if (_item.type() != Operation) | ||||
| 	{ | ||||
| 		assertThrow(_item.deposit() == 1, InvalidDeposit, ""); | ||||
|  | ||||
| @ -40,6 +40,10 @@ struct LinkerObject | ||||
| 	/// need to be replaced by the actual addresses by the linker.
 | ||||
| 	std::map<size_t, std::string> linkReferences; | ||||
| 
 | ||||
| 	/// Map from hashes of the identifiers of immutable variables to a list of offsets into the bytecode
 | ||||
| 	/// that refer to their values.
 | ||||
| 	std::map<u256, std::vector<size_t>> immutableReferences; | ||||
| 
 | ||||
| 	/// Appends the bytecode of @a _other and incorporates its link references.
 | ||||
| 	void append(LinkerObject const& _other); | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool | ||||
| 	case UndefinedItem: | ||||
| 	case Tag: | ||||
| 	case PushDeployTimeAddress: | ||||
| 	case AssignImmutable: | ||||
| 		return true; | ||||
| 	case Push: | ||||
| 	case PushString: | ||||
| @ -45,6 +46,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool | ||||
| 	case PushProgramSize: | ||||
| 	case PushData: | ||||
| 	case PushLibraryAddress: | ||||
| 	case PushImmutable: | ||||
| 		return false; | ||||
| 	case Operation: | ||||
| 	{ | ||||
|  | ||||
| @ -2012,6 +2012,16 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar | ||||
| 	return variablesAndOffsets; | ||||
| } | ||||
| 
 | ||||
| vector<VariableDeclaration const*> ContractType::immutableVariables() const | ||||
| { | ||||
| 	vector<VariableDeclaration const*> variables; | ||||
| 	for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts)) | ||||
| 		for (VariableDeclaration const* variable: contract->stateVariables()) | ||||
| 			if (variable->immutable()) | ||||
| 				variables.push_back(variable); | ||||
| 	return variables; | ||||
| } | ||||
| 
 | ||||
| vector<tuple<string, TypePointer>> ContractType::makeStackItems() const | ||||
| { | ||||
| 	if (m_super) | ||||
|  | ||||
| @ -895,6 +895,8 @@ public: | ||||
| 	/// @returns a list of all state variables (including inherited) of the contract and their
 | ||||
| 	/// offsets in storage.
 | ||||
| 	std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const; | ||||
| 	/// @returns a list of all immutable variables (including inherited) of the contract.
 | ||||
| 	std::vector<VariableDeclaration const*> immutableVariables() const; | ||||
| protected: | ||||
| 	std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override; | ||||
| private: | ||||
|  | ||||
| @ -71,6 +71,55 @@ void CompilerContext::addStateVariable( | ||||
| 	m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); | ||||
| } | ||||
| 
 | ||||
| void CompilerContext::addImmutable(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_runtimeContext, "Attempted to register an immutable variable for runtime code generation."); | ||||
| 	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 CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const | ||||
| { | ||||
| 	solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried."); | ||||
| 	solAssert(m_runtimeContext, "Attempted to fetch the memory offset of an immutable variable during runtime code generation."); | ||||
| 	return m_immutableVariables.at(&_variable); | ||||
| } | ||||
| 
 | ||||
| vector<string> CompilerContext::immutableVariableSlotNames(VariableDeclaration const& _variable) | ||||
| { | ||||
| 	string baseName = | ||||
| 		_variable.annotation().contract->fullyQualifiedName() + | ||||
| 		"." + | ||||
| 		_variable.name() + | ||||
| 		" (" + | ||||
| 		to_string(_variable.id()) + | ||||
| 		")"; | ||||
| 	solAssert(_variable.annotation().type->sizeOnStack() > 0, ""); | ||||
| 	if (_variable.annotation().type->sizeOnStack() == 1) | ||||
| 		return {baseName}; | ||||
| 	vector<string> names; | ||||
| 	auto collectSlotNames = [&](string const& _baseName, TypePointer type, auto const& _recurse) -> void { | ||||
| 		for (auto const& [slot, type]: type->stackItems()) | ||||
| 			if (type) | ||||
| 				_recurse(_baseName + " " + slot, type, _recurse); | ||||
| 			else | ||||
| 				names.emplace_back(_baseName); | ||||
| 	}; | ||||
| 	collectSlotNames(baseName, _variable.annotation().type, collectSlotNames); | ||||
| 	return names; | ||||
| } | ||||
| 
 | ||||
| size_t CompilerContext::reservedMemory() | ||||
| { | ||||
| 	solAssert(m_reservedMemory.has_value(), "Reserved memory was used before "); | ||||
| 	size_t reservedMemory = *m_reservedMemory; | ||||
| 	m_reservedMemory = std::nullopt; | ||||
| 	return reservedMemory; | ||||
| } | ||||
| 
 | ||||
| void CompilerContext::startFunction(Declaration const& _function) | ||||
| { | ||||
| 	m_functionCompilationQueue.startFunction(_function); | ||||
| @ -500,6 +549,13 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| LinkerObject const& CompilerContext::assembledObject() const | ||||
| { | ||||
| 	LinkerObject const& object = m_asm->assemble(); | ||||
| 	solAssert(object.immutableReferences.empty(), "Leftover immutables."); | ||||
| 	return object; | ||||
| } | ||||
| 
 | ||||
| FunctionDefinition const& CompilerContext::resolveVirtualFunction( | ||||
| 	FunctionDefinition const& _function, | ||||
| 	vector<ContractDefinition const*>::const_iterator _searchStart | ||||
|  | ||||
| @ -64,6 +64,7 @@ public: | ||||
| 		m_asm(std::make_shared<evmasm::Assembly>()), | ||||
| 		m_evmVersion(_evmVersion), | ||||
| 		m_revertStrings(_revertStrings), | ||||
| 		m_reservedMemory{0}, | ||||
| 		m_runtimeContext(_runtimeContext), | ||||
| 		m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector), | ||||
| 		m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector) | ||||
| @ -80,6 +81,16 @@ public: | ||||
| 	bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); } | ||||
| 
 | ||||
| 	void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); | ||||
| 	void addImmutable(VariableDeclaration const& _declaration); | ||||
| 
 | ||||
| 	/// @returns the reserved memory for storing the value of the immutable @a _variable during contract creation.
 | ||||
| 	size_t immutableMemoryOffset(VariableDeclaration const& _variable) const; | ||||
| 	/// @returns a list of slot names referring to the stack slots of an immutable variable.
 | ||||
| 	static std::vector<std::string> immutableVariableSlotNames(VariableDeclaration const& _variable); | ||||
| 
 | ||||
| 	/// @returns the reserved memory and resets it to mark it as used.
 | ||||
| 	size_t reservedMemory(); | ||||
| 
 | ||||
| 	void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); | ||||
| 	void removeVariable(Declaration const& _declaration); | ||||
| 	/// Removes all local variables currently allocated above _stackHeight.
 | ||||
| @ -217,6 +228,10 @@ public: | ||||
| 	evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } | ||||
| 	/// Appends the address (virtual, will be filled in by linker) of a library.
 | ||||
| 	void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } | ||||
| 	/// Appends an immutable variable. The value will be filled in by the constructor.
 | ||||
| 	void appendImmutable(std::string const& _identifier) { m_asm->appendImmutable(_identifier); } | ||||
| 	/// Appends an assignment to an immutable variable. Only valid in creation code.
 | ||||
| 	void appendImmutableAssignment(std::string const& _identifier) { m_asm->appendImmutableAssignment(_identifier); } | ||||
| 	/// Appends a zero-address that can be replaced by something else at deploy time (if the
 | ||||
| 	/// position in bytecode is known).
 | ||||
| 	void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); } | ||||
| @ -282,7 +297,7 @@ public: | ||||
| 		return m_asm->assemblyJSON(_indicies); | ||||
| 	} | ||||
| 
 | ||||
| 	evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); } | ||||
| 	evmasm::LinkerObject const& assembledObject() const; | ||||
| 	evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); } | ||||
| 
 | ||||
| 	/**
 | ||||
| @ -355,6 +370,12 @@ private: | ||||
| 	std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers; | ||||
| 	/// Storage offsets of state variables
 | ||||
| 	std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; | ||||
| 	/// Memory offsets reserved for the values of immutable variables during contract creation.
 | ||||
| 	std::map<VariableDeclaration const*, size_t> m_immutableVariables; | ||||
| 	/// Total amount of reserved memory. Reserved memory is used to store immutable variables during contract creation.
 | ||||
| 	/// This has to be finalized before initialiseFreeMemoryPointer() is called. That function
 | ||||
| 	/// will reset the optional to verify that.
 | ||||
| 	std::optional<size_t> m_reservedMemory = {0}; | ||||
| 	/// Offsets of local variables on the stack (relative to stack base).
 | ||||
| 	/// This needs to be a stack because if a modifier contains a local variable and this
 | ||||
| 	/// modifier is applied twice, the position of the variable needs to be restored
 | ||||
|  | ||||
| @ -51,7 +51,9 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi | ||||
| 
 | ||||
| void CompilerUtils::initialiseFreeMemoryPointer() | ||||
| { | ||||
| 	m_context << u256(generalPurposeMemoryStart); | ||||
| 	size_t reservedMemory = m_context.reservedMemory(); | ||||
| 	solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, ""); | ||||
| 	m_context << (u256(generalPurposeMemoryStart) + reservedMemory); | ||||
| 	storeFreeMemoryPointer(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -130,6 +130,8 @@ void ContractCompiler::initializeContext( | ||||
| 	m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); | ||||
| 	m_context.setOtherCompilers(_otherCompilers); | ||||
| 	m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); | ||||
| 	if (m_runtimeCompiler) | ||||
| 		registerImmutableVariables(_contract); | ||||
| 	CompilerUtils(m_context).initialiseFreeMemoryPointer(); | ||||
| 	registerStateVariables(_contract); | ||||
| 	m_context.resetVisitedNodes(&_contract); | ||||
| @ -183,10 +185,26 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont | ||||
| 	m_context << deployRoutine; | ||||
| 
 | ||||
| 	solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); | ||||
| 
 | ||||
| 	ContractType contractType(_contract); | ||||
| 	auto const& immutables = contractType.immutableVariables(); | ||||
| 	// Push all immutable values on the stack.
 | ||||
| 	for (auto const& immutable: immutables) | ||||
| 		CompilerUtils(m_context).loadFromMemory(m_context.immutableMemoryOffset(*immutable), *immutable->annotation().type); | ||||
| 	m_context.pushSubroutineSize(m_context.runtimeSub()); | ||||
| 	m_context << Instruction::DUP1; | ||||
| 	if (immutables.empty()) | ||||
| 		m_context << Instruction::DUP1; | ||||
| 	m_context.pushSubroutineOffset(m_context.runtimeSub()); | ||||
| 	m_context << u256(0) << Instruction::CODECOPY; | ||||
| 	// Assign immutable values from stack in reversed order.
 | ||||
| 	for (auto const& immutable: immutables | boost::adaptors::reversed) | ||||
| 	{ | ||||
| 		auto slotNames = m_context.immutableVariableSlotNames(*immutable); | ||||
| 		for (auto&& slotName: slotNames | boost::adaptors::reversed) | ||||
| 			m_context.appendImmutableAssignment(slotName); | ||||
| 	} | ||||
| 	if (!immutables.empty()) | ||||
| 		m_context.pushSubroutineSize(m_context.runtimeSub()); | ||||
| 	m_context << u256(0) << Instruction::RETURN; | ||||
| 
 | ||||
| 	return m_context.runtimeSub(); | ||||
| @ -521,6 +539,13 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac | ||||
| 		m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); | ||||
| } | ||||
| 
 | ||||
| void ContractCompiler::registerImmutableVariables(ContractDefinition const& _contract) | ||||
| { | ||||
| 	solAssert(m_runtimeCompiler, "Attempted to register immutables for runtime code generation."); | ||||
| 	for (auto const& var: ContractType(_contract).immutableVariables()) | ||||
| 		m_context.addImmutable(*var); | ||||
| } | ||||
| 
 | ||||
| void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract) | ||||
| { | ||||
| 	solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); | ||||
|  | ||||
| @ -99,6 +99,7 @@ private: | ||||
| 	void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); | ||||
| 
 | ||||
| 	void registerStateVariables(ContractDefinition const& _contract); | ||||
| 	void registerImmutableVariables(ContractDefinition const& _contract); | ||||
| 	void initializeStateVariables(ContractDefinition const& _contract); | ||||
| 
 | ||||
| 	bool visit(VariableDeclaration const& _variableDeclaration) override; | ||||
|  | ||||
| @ -2436,7 +2436,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex | ||||
| 	if (_variable.isConstant()) | ||||
| 		acceptAndConvert(*_variable.value(), *_variable.annotation().type); | ||||
| 	else if (_variable.immutable()) | ||||
| 		solUnimplemented(""); | ||||
| 		setLValue<ImmutableItem>(_expression, _variable); | ||||
| 	else | ||||
| 		setLValueFromDeclaration(_variable, _expression); | ||||
| } | ||||
|  | ||||
| @ -144,6 +144,42 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const | ||||
| 	m_context << Instruction::POP; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable): | ||||
| 	LValue(_compilerContext, _variable.annotation().type), m_variable(_variable) | ||||
| { | ||||
| 	solAssert(_variable.immutable(), ""); | ||||
| } | ||||
| 
 | ||||
| void ImmutableItem::retrieveValue(SourceLocation const&, bool) const | ||||
| { | ||||
| 	solUnimplementedAssert(m_dataType->isValueType(), ""); | ||||
| 	solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time."); | ||||
| 	for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable)) | ||||
| 		m_context.appendImmutable(slotName); | ||||
| } | ||||
| 
 | ||||
| void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const | ||||
| { | ||||
| 	CompilerUtils utils(m_context); | ||||
| 	solUnimplementedAssert(m_dataType->isValueType(), ""); | ||||
| 	solAssert(_sourceType.isValueType(), ""); | ||||
| 
 | ||||
| 	utils.convertType(_sourceType, *m_dataType, true); | ||||
| 	m_context << m_context.immutableMemoryOffset(m_variable); | ||||
| 	if (_move) | ||||
| 		utils.moveIntoStack(m_dataType->sizeOnStack()); | ||||
| 	else | ||||
| 		utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack()); | ||||
| 	utils.storeInMemoryDynamic(*m_dataType, false); | ||||
| 	m_context << Instruction::POP; | ||||
| } | ||||
| 
 | ||||
| void ImmutableItem::setToZero(SourceLocation const&, bool) const | ||||
| { | ||||
| 	solAssert(false, "Attempted to set immutable variable to zero."); | ||||
| } | ||||
| 
 | ||||
| StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): | ||||
| 	StorageItem(_compilerContext, *_declaration.annotation().type) | ||||
| { | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/codegen/ArrayUtils.h> | ||||
| #include <libsolutil/Common.h> | ||||
| #include <liblangutil/SourceLocation.h> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| @ -82,12 +83,12 @@ public: | ||||
| 
 | ||||
| 	unsigned sizeOnStack() const override { return 0; } | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	virtual void storeValue( | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	virtual void setToZero( | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
| @ -108,12 +109,12 @@ public: | ||||
| 	MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true); | ||||
| 	unsigned sizeOnStack() const override { return 1; } | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	virtual void storeValue( | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	virtual void setToZero( | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
| @ -122,6 +123,30 @@ private: | ||||
| 	bool m_padded = false; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Reference to an immutable variable. During contract creation this refers to a location in memory. At the | ||||
|  * end of contract creation the values from these memory locations are copied into all occurrences of the immutable | ||||
|  * variable in the runtime code. | ||||
|  */ | ||||
| class ImmutableItem: public LValue | ||||
| { | ||||
| public: | ||||
| 	ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable); | ||||
| 	unsigned sizeOnStack() const override { return 0; } | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
| private: | ||||
| 	VariableDeclaration const& m_variable; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>, | ||||
|  * where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied | ||||
| @ -136,12 +161,12 @@ public: | ||||
| 	StorageItem(CompilerContext& _compilerContext, Type const& _type); | ||||
| 	unsigned sizeOnStack() const override { return 2; } | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	virtual void storeValue( | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	virtual void setToZero( | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
| @ -158,12 +183,12 @@ public: | ||||
| 	StorageByteArrayElement(CompilerContext& _compilerContext); | ||||
| 	unsigned sizeOnStack() const override { return 2; } | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	virtual void storeValue( | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	virtual void setToZero( | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
| @ -180,12 +205,12 @@ public: | ||||
| 	TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues); | ||||
| 	unsigned sizeOnStack() const override; | ||||
| 	void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; | ||||
| 	virtual void storeValue( | ||||
| 	void storeValue( | ||||
| 		Type const& _sourceType, | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _move = false | ||||
| 	) const override; | ||||
| 	virtual void setToZero( | ||||
| 	void setToZero( | ||||
| 		langutil::SourceLocation const& _location = {}, | ||||
| 		bool _removeReference = true | ||||
| 	) const override; | ||||
|  | ||||
| @ -203,6 +203,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const | ||||
| 		EthAssemblyAdapter adapter(assembly); | ||||
| 		compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation); | ||||
| 		object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble()); | ||||
| 		yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables."); | ||||
| 		object.assembly = assembly.assemblyString(); | ||||
| 		object.sourceMappings = make_unique<string>( | ||||
| 			evmasm::AssemblyItem::computeSourceMapping( | ||||
|  | ||||
| @ -61,6 +61,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| 	Assembly _subAsm; | ||||
| 	auto sub_asm = make_shared<CharStream>("lorem ipsum", "sub.asm"); | ||||
| 	_subAsm.setSourceLocation({6, 8, sub_asm}); | ||||
| 	// PushImmutable
 | ||||
| 	_subAsm.appendImmutable("someImmutable"); | ||||
| 	_subAsm.append(Instruction::INVALID); | ||||
| 	shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm); | ||||
| 
 | ||||
| @ -86,6 +88,11 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| 	_assembly.pushSubroutineOffset(size_t(sub.data())); | ||||
| 	// PushDeployTimeAddress
 | ||||
| 	_assembly.append(PushDeployTimeAddress); | ||||
| 	// AssignImmutable.
 | ||||
| 	// Note that since there is no reference to "someOtherImmutable", this will compile to a simple POP in the hex output.
 | ||||
| 	_assembly.appendImmutableAssignment("someOtherImmutable"); | ||||
| 	_assembly.append(u256(2)); | ||||
| 	_assembly.appendImmutableAssignment("someImmutable"); | ||||
| 	// Operation
 | ||||
| 	_assembly.append(Instruction::STOP); | ||||
| 	_assembly.appendAuxiliaryDataToEnd(bytes{0x42, 0x66}); | ||||
| @ -95,8 +102,11 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| 
 | ||||
| 	BOOST_CHECK_EQUAL( | ||||
| 		_assembly.assemble().toHex(), | ||||
| 		"5b6001600220604673__$bf005014d9d0f534b8fcb268bd84c491a2$__" | ||||
| 		"600056603e6001603d73000000000000000000000000000000000000000000fe" | ||||
| 		"5b6001600220606f73__$bf005014d9d0f534b8fcb268bd84c491a2$__" | ||||
| 		"60005660676022604573000000000000000000000000000000000000000050" | ||||
| 		"60028060015250" | ||||
| 		"00fe" | ||||
| 		"7f0000000000000000000000000000000000000000000000000000000000000000" | ||||
| 		"fe010203044266eeaa" | ||||
| 	); | ||||
| 	BOOST_CHECK_EQUAL( | ||||
| @ -111,12 +121,16 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| 		"  dataSize(sub_0)\n" | ||||
| 		"  dataOffset(sub_0)\n" | ||||
| 		"  deployTimeAddress()\n" | ||||
| 		"  assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n" | ||||
| 		"  0x02\n" | ||||
| 		"  assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" | ||||
| 		"  stop\n" | ||||
| 		"stop\n" | ||||
| 		"data_a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b 01020304\n" | ||||
| 		"\n" | ||||
| 		"sub_0: assembly {\n" | ||||
| 		"        /* \"sub.asm\":6:8   */\n" | ||||
| 		"      immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" | ||||
| 		"      invalid\n" | ||||
| 		"}\n" | ||||
| 		"\n" | ||||
| @ -138,9 +152,104 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSHDEPLOYADDRESS\",\"source\":0}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"STOP\",\"source\":0}" | ||||
| 		"],\".data\":{\"0\":{\".code\":[{\"begin\":6,\"end\":8,\"name\":\"INVALID\",\"source\":1}]}," | ||||
| 		"\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"}}" | ||||
| 		"],\".data\":{\"0\":{\".code\":[" | ||||
| 		"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"}," | ||||
| 		"{\"begin\":6,\"end\":8,\"name\":\"INVALID\",\"source\":1}" | ||||
| 		"]},\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"}}" | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(immutable) | ||||
| { | ||||
| 	map<string, unsigned> indices = { | ||||
| 		{ "root.asm", 0 }, | ||||
| 		{ "sub.asm", 1 } | ||||
| 	}; | ||||
| 	Assembly _assembly; | ||||
| 	auto root_asm = make_shared<CharStream>("lorem ipsum", "root.asm"); | ||||
| 	_assembly.setSourceLocation({1, 3, root_asm}); | ||||
| 
 | ||||
| 	Assembly _subAsm; | ||||
| 	auto sub_asm = make_shared<CharStream>("lorem ipsum", "sub.asm"); | ||||
| 	_subAsm.setSourceLocation({6, 8, sub_asm}); | ||||
| 	_subAsm.appendImmutable("someImmutable"); | ||||
| 	_subAsm.appendImmutable("someOtherImmutable"); | ||||
| 	_subAsm.appendImmutable("someImmutable"); | ||||
| 	shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm); | ||||
| 
 | ||||
| 	_assembly.append(u256(42)); | ||||
| 	_assembly.appendImmutableAssignment("someImmutable"); | ||||
| 	_assembly.append(u256(23)); | ||||
| 	_assembly.appendImmutableAssignment("someOtherImmutable"); | ||||
| 
 | ||||
| 	auto sub = _assembly.appendSubroutine(_subAsmPtr); | ||||
| 	_assembly.pushSubroutineOffset(size_t(sub.data())); | ||||
| 
 | ||||
| 	checkCompilation(_assembly); | ||||
| 
 | ||||
| 	BOOST_CHECK_EQUAL( | ||||
| 		_assembly.assemble().toHex(), | ||||
| 		// root.asm
 | ||||
| 		// assign "someImmutable"
 | ||||
| 		"602a" // PUSH1 42 - value for someImmutable
 | ||||
| 		"80" // DUP1
 | ||||
| 		"6001" // PUSH1 1 - offset of first someImmutable in sub_0
 | ||||
| 		"52" // MSTORE
 | ||||
| 		"80" // DUP1
 | ||||
| 		"6043" // PUSH1 67 - offset of second someImmutable in sub_0
 | ||||
| 		"52" // MSTORE
 | ||||
| 		"50" // POP
 | ||||
| 		// assign "someOtherImmutable"
 | ||||
| 		"6017" // PUSH1 23 - value for someOtherImmutable
 | ||||
| 		"80" // DUP1
 | ||||
| 		"6022" // PUSH1 34 - offset of someOtherImmutable in sub_0
 | ||||
| 		"52" // MSTORE
 | ||||
| 		"50" // POP
 | ||||
| 		"6063" // PUSH1 0x63 - dataSize(sub_0)
 | ||||
| 		"6017" // PUSH1 0x17 - dataOffset(sub_0)
 | ||||
| 		"fe" // INVALID
 | ||||
| 		// end of root.asm
 | ||||
| 		// sub.asm
 | ||||
| 		"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someImmutable - data at offset 1
 | ||||
| 		"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someOtherImmutable - data at offset 34
 | ||||
| 		"7f0000000000000000000000000000000000000000000000000000000000000000" // PUSHIMMUTABLE someImmutable - data at offset 67
 | ||||
| 	); | ||||
| 	BOOST_CHECK_EQUAL( | ||||
| 		_assembly.assemblyString(), | ||||
| 		"    /* \"root.asm\":1:3   */\n" | ||||
| 		"  0x2a\n" | ||||
| 		"  assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" | ||||
| 		"  0x17\n" | ||||
| 		"  assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n" | ||||
| 		"  dataSize(sub_0)\n" | ||||
| 		"  dataOffset(sub_0)\n" | ||||
| 		"stop\n" | ||||
| 		"\n" | ||||
| 		"sub_0: assembly {\n" | ||||
| 		"        /* \"sub.asm\":6:8   */\n" | ||||
| 		"      immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" | ||||
| 		"      immutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n" | ||||
| 		"      immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" | ||||
| 		"}\n" | ||||
| 	); | ||||
| 	BOOST_CHECK_EQUAL( | ||||
| 		util::jsonCompactPrint(_assembly.assemblyJSON(indices)), | ||||
| 		"{\".code\":[" | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"17\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," | ||||
| 		"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}" | ||||
| 		"],\".data\":{\"0\":{\".code\":[" | ||||
| 		"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"}," | ||||
| 		"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someOtherImmutable\"}," | ||||
| 		"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"}" | ||||
| 		"]}}}" | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -112,6 +112,44 @@ namespace | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE(Optimiser) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(cse_push_immutable_same) | ||||
| { | ||||
| 	AssemblyItem pushImmutable{PushImmutable, 0x1234}; | ||||
| 	checkCSE({pushImmutable, pushImmutable}, {pushImmutable, Instruction::DUP1}); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(cse_push_immutable_different) | ||||
| { | ||||
| 	AssemblyItems input{{PushImmutable, 0x1234},{PushImmutable, 0xABCD}}; | ||||
| 	checkCSE(input, input); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(cse_assign_immutable) | ||||
| { | ||||
| 	{ | ||||
| 		AssemblyItems input{u256(0x42), {AssignImmutable, 0x1234}}; | ||||
| 		checkCSE(input, input); | ||||
| 	} | ||||
| 	{ | ||||
| 		AssemblyItems input{{AssignImmutable, 0x1234}}; | ||||
| 		checkCSE(input, input); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks) | ||||
| { | ||||
| 	AssemblyItems input = addDummyLocations(AssemblyItems{ | ||||
| 		u256(0x42), | ||||
| 		{AssignImmutable, 0x1234}, | ||||
| 		Instruction::ORIGIN | ||||
| 	}); | ||||
| 
 | ||||
| 	evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()}; | ||||
| 	// Make sure CSE breaks after AssignImmutable.
 | ||||
| 	BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(cse_intermediate_swap) | ||||
| { | ||||
| 	evmasm::KnownState state; | ||||
| @ -798,6 +836,68 @@ BOOST_AUTO_TEST_CASE(block_deduplicator) | ||||
| 	BOOST_CHECK_EQUAL(pushTags.size(), 2); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_same) | ||||
| { | ||||
| 	AssemblyItems blocks{ | ||||
| 		AssemblyItem(Tag, 1), | ||||
| 		u256(42), | ||||
| 		AssemblyItem{AssignImmutable, 0x1234}, | ||||
| 		Instruction::JUMP, | ||||
| 		AssemblyItem(Tag, 2), | ||||
| 		u256(42), | ||||
| 		AssemblyItem{AssignImmutable, 0x1234}, | ||||
| 		Instruction::JUMP | ||||
| 	}; | ||||
| 
 | ||||
| 	AssemblyItems input = AssemblyItems{ | ||||
| 		AssemblyItem(PushTag, 2), | ||||
| 		AssemblyItem(PushTag, 1), | ||||
| 	} + blocks; | ||||
| 	AssemblyItems output = AssemblyItems{ | ||||
| 		AssemblyItem(PushTag, 1), | ||||
| 		AssemblyItem(PushTag, 1), | ||||
| 	} + blocks; | ||||
| 	BlockDeduplicator dedup(input); | ||||
| 	dedup.deduplicate(); | ||||
| 	BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_value) | ||||
| { | ||||
| 	AssemblyItems input{ | ||||
| 		AssemblyItem(PushTag, 2), | ||||
| 		AssemblyItem(PushTag, 1), | ||||
| 		AssemblyItem(Tag, 1), | ||||
| 		u256(42), | ||||
| 		AssemblyItem{AssignImmutable, 0x1234}, | ||||
| 		Instruction::JUMP, | ||||
| 		AssemblyItem(Tag, 2), | ||||
| 		u256(23), | ||||
| 		AssemblyItem{AssignImmutable, 0x1234}, | ||||
| 		Instruction::JUMP | ||||
| 	}; | ||||
| 	BlockDeduplicator dedup(input); | ||||
| 	BOOST_CHECK(!dedup.deduplicate()); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_hash) | ||||
| { | ||||
| 	AssemblyItems input{ | ||||
| 		AssemblyItem(PushTag, 2), | ||||
| 		AssemblyItem(PushTag, 1), | ||||
| 		AssemblyItem(Tag, 1), | ||||
| 		u256(42), | ||||
| 		AssemblyItem{AssignImmutable, 0x1234}, | ||||
| 		Instruction::JUMP, | ||||
| 		AssemblyItem(Tag, 2), | ||||
| 		u256(42), | ||||
| 		AssemblyItem{AssignImmutable, 0xABCD}, | ||||
| 		Instruction::JUMP | ||||
| 	}; | ||||
| 	BlockDeduplicator dedup(input); | ||||
| 	BOOST_CHECK(!dedup.deduplicate()); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(block_deduplicator_loops) | ||||
| { | ||||
| 	AssemblyItems input{ | ||||
|  | ||||
| @ -0,0 +1,20 @@ | ||||
| contract D { | ||||
| 	function f() external view returns (uint256) { | ||||
| 		return 42; | ||||
| 	} | ||||
| } | ||||
| contract C { | ||||
| 	D d; | ||||
| 	function() external view returns(uint256) immutable z; | ||||
| 	constructor() public { | ||||
| 		d = new D(); | ||||
| 		z = d.f; | ||||
| 	} | ||||
| 	function f() public view returns (uint256) { | ||||
| 	assert(z.address == address(d)); | ||||
| 	assert(z.selector == D.f.selector); | ||||
| 		return z(); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // f() -> 42 | ||||
							
								
								
									
										30
									
								
								test/libsolidity/semanticTests/immutable/inheritance.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								test/libsolidity/semanticTests/immutable/inheritance.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| contract A { | ||||
| 	uint8 immutable a; | ||||
| 	constructor() public { | ||||
| 		a = 4; | ||||
| 	} | ||||
| } | ||||
| contract B is A { | ||||
| 	uint8 immutable b; | ||||
| 	constructor() public { | ||||
| 		b = 3; | ||||
| 	} | ||||
| } | ||||
| contract C is A { | ||||
| 	uint8 immutable c; | ||||
| 	constructor() public { | ||||
| 		c = 2; | ||||
| 	} | ||||
| } | ||||
| contract D is B, C { | ||||
| 	uint8 immutable d; | ||||
| 
 | ||||
| 	constructor() public { | ||||
| 		d = 1; | ||||
| 	} | ||||
| 	function f() public view returns (uint256, uint256, uint, uint) { | ||||
| 		return (a, b, c, d); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // f() -> 4, 3, 2, 1 | ||||
| @ -0,0 +1,15 @@ | ||||
| contract C { | ||||
| 	function() internal view returns(uint256) immutable z; | ||||
| 	constructor() public { | ||||
| 		z = f; | ||||
| 	} | ||||
| 	function f() public view returns (uint256) { | ||||
| 		return 7; | ||||
| 	} | ||||
| 	function callZ() public view returns (uint) { | ||||
| 		return z(); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // f() -> 7 | ||||
| // callZ() -> 7 | ||||
							
								
								
									
										31
									
								
								test/libsolidity/semanticTests/immutable/multi_creation.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								test/libsolidity/semanticTests/immutable/multi_creation.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| contract A { | ||||
| 	uint immutable a; | ||||
| 	constructor() public { | ||||
| 		a = 7; | ||||
| 	} | ||||
| 	function f() public view returns (uint) { return a; } | ||||
| } | ||||
| contract B { | ||||
| 	uint immutable a; | ||||
| 	constructor() public { | ||||
| 		a = 5; | ||||
| 	} | ||||
| 	function f() public view returns (uint) { return a; } | ||||
| } | ||||
| contract C { | ||||
| 	uint immutable a; | ||||
| 	uint public x; | ||||
| 	uint public y; | ||||
| 	constructor() public { | ||||
| 		a = 3; | ||||
| 		x = (new A()).f(); | ||||
| 		y = (new B()).f(); | ||||
| 	} | ||||
| 	function f() public returns (uint256, uint, uint) { | ||||
| 		return (a, (new A()).f(), (new B()).f()); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // f() -> 3, 7, 5 | ||||
| // x() -> 7 | ||||
| // y() -> 5 | ||||
							
								
								
									
										13
									
								
								test/libsolidity/semanticTests/immutable/stub.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								test/libsolidity/semanticTests/immutable/stub.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| contract C { | ||||
| 	uint256 immutable x; | ||||
| 	uint256 immutable y; | ||||
| 	constructor() public { | ||||
| 		x = 42; | ||||
| 		y = 23; | ||||
| 	} | ||||
| 	function f() public view returns (uint256, uint256) { | ||||
| 		return (x+x,y); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // f() -> 84, 23 | ||||
							
								
								
									
										19
									
								
								test/libsolidity/semanticTests/immutable/use_scratch.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								test/libsolidity/semanticTests/immutable/use_scratch.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| contract C { | ||||
| 	uint256 immutable x; | ||||
| 	uint256 immutable y; | ||||
| 	mapping(uint => uint) public m; | ||||
| 	constructor(uint _a) public { | ||||
| 		x = 42; | ||||
| 		y = 23; | ||||
| 		m[_a] = 7; | ||||
| 		new uint[](4); | ||||
| 
 | ||||
| 	} | ||||
| 	function f() public view returns (uint256, uint256) { | ||||
| 		return (x+x,y); | ||||
| 	} | ||||
| } | ||||
| // ---- | ||||
| // constructor(): 3 -> | ||||
| // f() -> 84, 23 | ||||
| // m(uint256): 3 -> 7 | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user