[Ethereum] How to return dynamic sized array’s original address on the memory of Solidity

assemblygo-ethereummemorysolidity

On the following source code, memOffset returns local values such as 96, 128 as the address value. How could I make it return the original address that is located on the memory, is it possible? After function f() is called, is the memory freed like local variables in C?

Complete source code is here

contract C {
    function f(uint a, uint b) constant returns (uint[]) {
        assembly {
            // Create an dynamic sized array manually.
            let memOffset := mload(0x40) // 0x40 is the address where next free memory slot is stored in Solidity.
            mstore(memOffset, 0x20) // single dimensional array, data offset is 0x20
            mstore(add(memOffset, 32), 2) // Set size to 2
            mstore(add(memOffset, 64), a) // array[0] = a
            mstore(add(memOffset, 96), b) // array[1] = b
            return(memOffset, 128)
        }
    }
}

Best Answer

Revised as previous answer wasn't completely true as it is possible in memory, but it can only be done in assembly and is quite involved. I've been looking at this quite a lot recently and your issue is primarily down to a memory pointer. There is two reasons why the above won't work in direct memory; the first is the msize opcode should point to the largest accessed memory index, but it doesn't which is being overwritten. The second is that we need to discard the data type size as this will automatically be allocated by the EVM.

As you can see from your example mload is using 0x40 (in memory this will reference bytes 64-96) which is the currently allocated memory size (msize) and is making the msize point to 0x20 (byte 32).

Layout in memory

Solidity reserves three 256-bit slots:

0 - 64: scratch space for hashing methods

64 - 96: currently allocated memory size (aka. free memory pointer)

Scratch space can be used between statements (ie. within inline assembly).

Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).

Source: http://solidity.readthedocs.io/en/develop/miscellaneous.html#layout-in-memory

msize

"size of memory, i.e. largest accessed memory index"

Source : http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

From the source code:

msize (note that msize also depends on memory read access)

Source: https://github.com/ethereum/solidity/blob/18a72dbe4668e23aaf38404183b978fbbb1824d1/libevmasm/SemanticInformation.cpp#L63

Example of a test resetting the msize:

(mstore 0x40 0)     ; Set initial MSIZE to 0x60

Source: https://github.com/ethereum/solidity/blob/6cbb726fb8970c6cb98e9b6a2928ef612ad9d760/test/liblll/EndToEndTest.cpp#L641

Because the code uses a return, it halts execution and returns the value stored in memory which is being referenced.

"end execution, return data mem[p..(p+s))"

Source : http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

This will freeze the state of the memory and return the value of the memory which you reference.

In memory solution

Let's look at returning a broken value by altering the code to something like:

contract C {

    function c() public returns (uint[]) {
        return f(1,2);
    }

    function f(uint a, uint b) private returns (uint[] memory memOffset) {
        assembly {
             // Create an dynamic sized array manually.
             // Don't need to define the data type here as the EVM will prefix it
             mstore(add(memOffset, 0x00), 2) // Set size to 2
             mstore(add(memOffset, 0x20), a) // array[0] = a
             mstore(add(memOffset, 0x40), b) // array[1] = b
        }
    }
}

We now get an array with invalid values, this is due to additional operations being executed by the contract which is overwriting the memory you allocated. To fix this we need to move where we are writing memory to and let the evm know where the msize should be pointing to.

contract C {

    function c() public returns (uint[]) {
        return f(1,2);
    }

    function f(uint a, uint b) private returns (uint[] memory memOffset) {
        assembly {
             // Create an dynamic sized array manually.
             // Don't need to define the data type here as the EVM will prefix it
             memOffset := msize() // Get the highest available block of memory
             mstore(add(memOffset, 0x00), 2) // Set size to 2
             mstore(add(memOffset, 0x20), a) // array[0] = a
             mstore(add(memOffset, 0x40), b) // array[1] = b
             mstore(0x40, add(memOffset, 0x60)) // Update the msize offset to be our memory reference plus the amount of bytes we're using
        }
    }
}

The above should now return the correct value.

Previous answer:

Dynamic arrays cannot be stored in memory due to their nondeterministic size. Basically, if you have a dynamic array which has a incremental growing and shrinking storage you would have to either constantly keep shuffling around the memory, risk overwriting existing memory or reserve a large block of memory which is incredibly inefficient and gas heavy. Other programming languages resolved this issue using dictionaries and linked list structures where you reference various memory locations instead of a sequence of memory. The reason why its possible to do in the stack/storage is because it optimizes the storage similar to a linked list, but this hasn't been applied to the use within memory. In short arrays in Solidity are far from complete and need some work yet, so I would consider using other techniques such as storing data as bytes.

Related Topic