mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	More specific push implementation.
This commit is contained in:
		
							parent
							
								
									efa0ccaf9e
								
							
						
					
					
						commit
						65f18a18de
					
				| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| Features: | ||||
|  * Code Generator: Initialize arrays without using ``msize()``. | ||||
|  * Code Generator: More specialized and thus optimized implementation for ``x.push(...)`` | ||||
|  * Commandline interface: Error when missing or inaccessible file detected. Suppress it with the ``--ignore-missing`` flag. | ||||
|  * General: Support accessing dynamic return data in post-byzantium EVMs. | ||||
|  * Interfaces: Allow overriding external functions in interfaces with public in an implementing contract. | ||||
|  | ||||
| @ -774,6 +774,55 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const | ||||
| { | ||||
| 	solAssert(_type.location() == DataLocation::Storage, ""); | ||||
| 	solAssert(_type.isDynamicallySized(), ""); | ||||
| 	if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) | ||||
| 		solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); | ||||
| 
 | ||||
| 	if (_type.isByteArray()) | ||||
| 	{ | ||||
| 		// We almost always just add 2 (length of byte arrays is shifted left by one)
 | ||||
| 		// except for the case where we transition from a short byte array
 | ||||
| 		// to a long byte array, there we have to copy.
 | ||||
| 		// This happens if the length is exactly 31, which means that the
 | ||||
| 		// lowest-order byte (we actually use a mask with fewer bits) must
 | ||||
| 		// be (31*2+0) = 62
 | ||||
| 
 | ||||
| 		m_context.appendInlineAssembly(R"({ | ||||
| 			let data := sload(ref) | ||||
| 			let shifted_length := and(data, 63) | ||||
| 			// We have to copy if length is exactly 31, because that marks
 | ||||
| 			// the transition between in-place and out-of-place storage.
 | ||||
| 			switch shifted_length | ||||
| 			case 62 | ||||
| 			{ | ||||
| 				mstore(0, ref) | ||||
| 				let data_area := keccak256(0, 0x20) | ||||
| 				sstore(data_area, and(data, not(0xff))) | ||||
| 				// New length is 32, encoded as (32 * 2 + 1)
 | ||||
| 				sstore(ref, 65) | ||||
| 				// Replace ref variable by new length
 | ||||
| 				ref := 32 | ||||
| 			} | ||||
| 			default | ||||
| 			{ | ||||
| 				sstore(ref, add(data, 2)) | ||||
| 				// Replace ref variable by new length
 | ||||
| 				if iszero(and(data, 1)) { data := shifted_length } | ||||
| 				ref := add(div(data, 2), 1) | ||||
| 			} | ||||
| 		})", {"ref"}); | ||||
| 	} | ||||
| 	else | ||||
| 		m_context.appendInlineAssembly(R"({ | ||||
| 			let new_length := add(sload(ref), 1) | ||||
| 			sstore(ref, new_length) | ||||
| 			ref := new_length | ||||
| 		})", {"ref"}); | ||||
| } | ||||
| 
 | ||||
| void ArrayUtils::clearStorageLoop(TypePointer const& _type) const | ||||
| { | ||||
| 	m_context.callLowLevelFunction( | ||||
|  | ||||
| @ -67,6 +67,12 @@ public: | ||||
| 	/// Stack pre: reference (excludes byte offset) new_length
 | ||||
| 	/// Stack post:
 | ||||
| 	void resizeDynamicArray(ArrayType const& _type) const; | ||||
| 	/// Increments the size of a dynamic array by one.
 | ||||
| 	/// Does not touch the new data element. In case of a byte array, this might move the
 | ||||
| 	/// data.
 | ||||
| 	/// Stack pre: reference (excludes byte offset)
 | ||||
| 	/// Stack post: new_length
 | ||||
| 	void incrementDynamicArraySize(ArrayType const& _type) const; | ||||
| 	/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
 | ||||
| 	/// Stack pre: end_ref start_ref
 | ||||
| 	/// Stack post: end_ref
 | ||||
|  | ||||
| @ -821,24 +821,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) | ||||
| 				function.kind() == FunctionType::Kind::ArrayPush ? | ||||
| 				make_shared<ArrayType>(DataLocation::Storage, paramType) : | ||||
| 				make_shared<ArrayType>(DataLocation::Storage); | ||||
| 			// get the current length
 | ||||
| 			ArrayUtils(m_context).retrieveLength(*arrayType); | ||||
| 			m_context << Instruction::DUP1; | ||||
| 			// stack: ArrayReference currentLength currentLength
 | ||||
| 			m_context << u256(1) << Instruction::ADD; | ||||
| 			// stack: ArrayReference currentLength newLength
 | ||||
| 			m_context << Instruction::DUP3 << Instruction::DUP2; | ||||
| 			ArrayUtils(m_context).resizeDynamicArray(*arrayType); | ||||
| 			m_context << Instruction::SWAP2 << Instruction::SWAP1; | ||||
| 			// stack: newLength ArrayReference oldLength
 | ||||
| 			ArrayUtils(m_context).accessIndex(*arrayType, false); | ||||
| 
 | ||||
| 			// stack: newLength storageSlot slotOffset
 | ||||
| 			// stack: ArrayReference
 | ||||
| 			arguments[0]->accept(*this); | ||||
| 			TypePointer const& argType = arguments[0]->annotation().type; | ||||
| 			// stack: ArrayReference argValue
 | ||||
| 			utils().moveToStackTop(argType->sizeOnStack(), 1); | ||||
| 			// stack: argValue ArrayReference
 | ||||
| 			m_context << Instruction::DUP1; | ||||
| 			ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); | ||||
| 			// stack: argValue ArrayReference newLength
 | ||||
| 			m_context << Instruction::SWAP1; | ||||
| 			// stack: argValue newLength ArrayReference
 | ||||
| 			m_context << u256(1) << Instruction::DUP3 << Instruction::SUB; | ||||
| 			// stack: argValue newLength ArrayReference (newLength-1)
 | ||||
| 			ArrayUtils(m_context).accessIndex(*arrayType, false); | ||||
| 			// stack: argValue newLength storageSlot slotOffset
 | ||||
| 			utils().moveToStackTop(3, argType->sizeOnStack()); | ||||
| 			// stack: newLength storageSlot slotOffset argValue
 | ||||
| 			TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); | ||||
| 			solAssert(type, ""); | ||||
| 			utils().convertType(*arguments[0]->annotation().type, *type); | ||||
| 			utils().convertType(*argType, *type); | ||||
| 			utils().moveToStackTop(1 + type->sizeOnStack()); | ||||
| 			utils().moveToStackTop(1 + type->sizeOnStack()); | ||||
| 			// stack: newLength argValue storageSlot slotOffset
 | ||||
|  | ||||
| @ -4884,6 +4884,48 @@ BOOST_AUTO_TEST_CASE(array_push) | ||||
| 	ABI_CHECK(callContractFunction("test()"), encodeArgs(5, 4, 3, 3)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(array_push_struct) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract c { | ||||
| 			struct S { uint16 a; uint16 b; uint16[3] c; uint16[] d; } | ||||
| 			S[] data; | ||||
| 			function test() returns (uint16, uint16, uint16, uint16) { | ||||
| 				S memory s; | ||||
| 				s.a = 2; | ||||
| 				s.b = 3; | ||||
| 				s.c[2] = 4; | ||||
| 				s.d = new uint16[](4); | ||||
| 				s.d[2] = 5; | ||||
| 				data.push(s); | ||||
| 				return (data[0].a, data[0].b, data[0].c[2], data[0].d[2]); | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	ABI_CHECK(callContractFunction("test()"), encodeArgs(2, 3, 4, 5)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(array_push_packed_array) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract c { | ||||
| 			uint80[] x; | ||||
| 			function test() returns (uint80, uint80, uint80, uint80) { | ||||
| 				x.push(1); | ||||
| 				x.push(2); | ||||
| 				x.push(3); | ||||
| 				x.push(4); | ||||
| 				x.push(5); | ||||
| 				x.length = 4; | ||||
| 				return (x[0], x[1], x[2], x[3]); | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	ABI_CHECK(callContractFunction("test()"), encodeArgs(1, 2, 3, 4)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(byte_array_push) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| @ -4904,6 +4946,29 @@ BOOST_AUTO_TEST_CASE(byte_array_push) | ||||
| 	ABI_CHECK(callContractFunction("test()"), encodeArgs(false)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(byte_array_push_transition) | ||||
| { | ||||
| 	// Tests transition between short and long encoding
 | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract c { | ||||
| 			bytes data; | ||||
| 			function test() returns (uint) { | ||||
| 				for (uint i = 1; i < 40; i++) | ||||
| 				{ | ||||
| 					data.push(byte(i)); | ||||
| 					if (data.length != i) return 0x1000 + i; | ||||
| 					if (data[data.length - 1] != byte(i)) return i; | ||||
| 				} | ||||
| 				for (i = 1; i < 40; i++) | ||||
| 					if (data[i - 1] != byte(i)) return 0x1000000 + i; | ||||
| 				return 0; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode); | ||||
| 	ABI_CHECK(callContractFunction("test()"), encodeArgs(0)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(external_array_args) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
|  | ||||
| @ -627,8 +627,8 @@ BOOST_AUTO_TEST_CASE(optimise_multi_stores) | ||||
| 	)"; | ||||
| 	compileBothVersions(sourceCode); | ||||
| 	compareVersions("f()"); | ||||
| 	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 13); | ||||
| 	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 11); | ||||
| 	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 9); | ||||
| 	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 8); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE_END() | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user