How does memory work in Yul?
Written by Mark Jonathas
Reviewed by Brady Werkheiser
Yul is an intermediate programming language that can be used to write a form of assembly language inside smart contracts. Once you’ve learned about Yul storage and how to read and write packed storage variables, it’s important to learn how memory works with Yul smart contracts.
How does memory work in Yul?
Memory behaves differently than storage. Memory is not persistent, which means that once the function is done executing all of the variables are cleared.
Memory is comparable to heap in other languages, but there is no garbage collector.
Memory is a lot cheaper than storage. The first 22 words of memory costs are calculated linearly, but be careful because after that memory costs become quadratic.
Memory is laid out in 32 byte sequences. We will get a better understanding of this later, but for now understand 0x00 - 0x20 is one sequence (you can think of it like a slot if that helps, but they are different).
Solidity allocates 0x00 - 0x40 as scratch space. This area of memory is not guaranteed to be empty, and is used for certain operations.
0x40 - 0x60 stores the location of what is known as the free memory pointer, which is used to write something new to memory.
0x60 - 0x80 is left empty as a gap.
0x80 is where we begin our operations.
Memory does not pack values. Retrieving values from storage will be stored in their own 32 byte sequence (i.e 0x80-0xa0).
What operations use memory in Yul?
Memory is used for the following operations:
Return values for external calls
Set function values for external calls
Get values from external calls
Revert with an error string
Log messages
Hash with keccak256()
Create other smart contracts
Here are some useful Yul instructions for memory:
Let’s check out some more data structures!
How to Use Structs and Memory in Yul
Structs and fixed arrays actually behave the same but since we already looked at fixed arrays in the storage section, we are going to look at structs here. Look at the following struct.
struct Var10 {
uint256 subVar1;
uint256 subVar2;
}
Nothing unusual about this, just a simple struct.
Now let’s look at some code!
function getStructValues() external pure returns(uint256, uint256) {
// initialize struct
Var10 memory s;
s.subVar1 = 32;
s.subVar2 = 64;
assembly {
return( 0x80, 0xc0 )
}
}
Here we are setting s.subVar1 to memory location 0x80 - 0xa0 and s.subVar2 to memory location 0xa0 - 0xc0. That is why we are returning 0x80 - 0xc0. Here is a table of the memory layout right before the end of the transaction.
Things to take away from this:
0x00 - 0x40 is empty for scratch space
0x40 gives us the free memory pointer
Solidity leaves a gap for 0x60
0x80 and 0xa0 are used for storing the values of the struct
0xc0 is the new free memory pointer
In this last part of the memory section I want to show you how dynamic arrays work in memory. We are going to pass [0, 1, 2, 3] as the parameter arr for this example. As an added bonus for this example we are going to add an extra element to the array. Be careful doing this in production as you may overwrite a different memory variable.
Here is the code:
unction getDynamicArray(uint256[] memory arr) external view returns (uint256[] memory) {
assembly {
// where array is stored in memory (0x80)
let location := arr
// length of array is stored at arr (4)
let length := mload(arr)
// gets next available memory location
let nextMemoryLocation := add( add( location, 0x20 ), mul( length, 0x20 ) )
// stores new value to memory
mstore(nextMemoryLocation, 4)
// increment length by 1
length := add( length, 1 )
// store new length value
mstore(location, length)
// update free memory pointer
mstore(0x40, 0x140)
return ( add( location, 0x20 ) , mul( length, 0x20 ) )
}
}
Here is what’s happening:
Get where the array is stored in memory
Get the length of the array, which is stored in the first memory location of the array
Add 32 bytes to the location (skip the length of the array) to see the next available location
Multiply the length of the array by 32 bytes to advance us to the next memory location
Store our new value (4)
Update the length of the array by one.
Update the free memory pointer
Return the array.
Let’s look at the memory layout once again.
That concludes the section on memory!
Next up, learn how to call smart contracts using Yul or go back to learn how to read and write packed storage variables!
Related overviews
What it is, How it Works, and How to Get Started
Explore the Best Free and Paid Courses for Learning Solidity Development
Your Guide to Getting Started With Solidity Arrays—Functions, Declaring, and Troubleshooting