Layout of Memory in EVM

Justin Thein Kyaw
11 min readOct 17, 2023

--

1. Overall understanding of How’s Memory Works?

1.1 Linear Address Space

EVM Memory က Linear bytes array လို့ ပုံဖော်လို့ရတယ်။ Memory ကို Contract execution လုပ်တဲ့အချိန်မှာ Temporary data storage အနေနဲ့အလုပ်လုပ်တယ်။ Gas ရှိရင်ရှိသလောက် Memory Length ကိုယူလို့ရတယ်။

1.2 Dynamic Allocation

EVM memory ကို Contract Execution time မှာလိုရင်လိုသလို allocate လုပ်တယ်။ ဘယ် offset ကနေစပြီး Memory size ဘယ်လောက်ထိယူမလဲဆိုတာ EVM ကိုပြောပြီး Allocate လုပ်တယ်။

mstore, mstore8 တို့ mload စတဲ့ opcodes တွေကိုသုံးပြီး Allocate လုပ်တယ်။

ဒီနေရာမှာ Allocate လုပ်တယ်ဆိုတာက တခြား Low-level code တွေအလုပ်လုပ်တာနဲ့ ကွာခြားနိုင်ပါတယ်။

  • တခြား Low-level code တွေမှာကက Memory မှာ allocate လုပ်ချင်ရင် Environment ကို Permission တောင်းပြီး Permission granted ဖြစ်တော့မှ Memory allocate လုပ်ခွင့်ရမှာဖြစ်ပြီး Program ဆက် Run မှာဖြစ်ပါတယ်။
  • EVM မှာက Memory allocate လုပ်တာ Manually လုပ်ဖို့မလိုဘူး။ Environment ကို ကိုယ်ဘယ်လောက်သုံးမယ်ဆိုတာပြောပီးတာနဲ့ တန်းသုံးရုံပါပဲ
  • (Overall ကြည့်ရင် ဒီ၂ချက်က သိပ်မကွာသလိုခံစားရပေမယ့် Low level code အလုပ်လုပ်ပုံတွေအရ ကွာခြားချက်နဲ့ တခြား Pros and cons တွေရှိလာပါတယ်)

1.3 Word-Based

အစောကပြောခဲ့သလို EVM က Linear Bytes-Array ကြီးဖြစ်ပါတယ်။ သူ့ Index တွေက Memory address ပါ။ အဲတော့ Array ၁ ခန်းကို 32-byte “word” ကျယ်ပါတယ်။ Memory မှာ 32 bytes ကို word size လို့ခေါ်ဝေါ်သုံးစွဲပါတယ်။

ဒါကြောင့် Memory မှာ Data ကို Access လုပ်တာ Store တဲ့အခါ Index (offset) တွက်တာကို 32 bytes ရဲ့ Multiple (multiple of 32 — Math terms) အနေနဲ့တွက်တာက ပိုမို လွယ်ကူစေပါတယ်

For example:

>>> int('0x00', 16)
0
>>> int('0x20', 16)
32 # = 0 + 32
>>> int('0x40', 16)
64 # = 32 + 32
>>> int('0x60', 16)
96 # = 64 + 32
>>> int('0x80', 16)
128 # = 96 + 32

# It's look like this in virtualization #
# index start from 0x00, value is 256-bits wide.
{
"0x0": "000...0000000",
"0x20": "000...000000",
"0x40": "000...0000000",
"0x60": "000...0000000",
"0x80": "000...0000000",
"0xa0": "000...0000000",
...
}

1.4 Stack and Memory interaction

Contract တွေ ကို Execution လုပ်တဲ့အခါ Stack နဲ့ Memory ကိုအဓိကထားပီးအလုပ်လုပ်ပါတယ်။ (Like fetch–decode–execute cycle). ဥပမာ Stack ပေါ်ကိုလိုတဲ့ Data ကိုတင်မယ် execution လုပ်မယ် ပီးရင် Memory မှာသိမ်းမယ်။ လိုရင် Memory ပေါ်ကဟာကို Stack ပေါ်တင်မယ် စသည်ဖြင့်ပေါ့။

1.5 Gas Cost

EVM Memory ကိုအသုံးပြုရမှာလည်း သုံးတဲ့ Opcode ပေါ်မူတည်ပီး Storage ကိုသုံးသလောက်မများပေမယ့် Gas အကုန်အကျရှိတယ်။

1.6 Limited Size

Ethereum ရဲ့ Yellow paper အရ Memory size ကကြိုက်သလောက်ယူလို့ရတယ်ဆိုပေမယ့် Infinite မဟုတ်ပါဘူး။ အပေါ်ကပြောခဲ့သလို Gas cost ရှိတဲ့အတွက် Transaction မှာပါတဲ့ Gas ကုန်ရင် Program က exit ဖြစ်သွားမှာပါ။ ဒီတော့ Gas supply များလေလေ Contract က Memory ပိုယူနိုင်လေလေဖြစ်ပါတယ်။

Memory Layout ကိုစမ်းဖို့ အောက်ပါကုတ်ကို အသုံးပြုပီး Illustrated လုပ်လို့ရပါတယ်။

pragma solidity ^0.8.0;

contract MemoryExample {
function addToMemory(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result;
assembly {
// Allocate 64 bytes (2 words) of memory starting at position 0
mstore(0, a)
mstore(32, b)
// Add the two values and store the result in memory
result := add(mload(0), mload(32))
}
return result;
}
}

2. Detail Layout of Memory

2.1 Memory Use case (Why memory?)

Returning data

  • Code က return ပြန်ဖို့လိုလာပြီဆိုရင် Memory ကနေ Data ကိုယူပီး Return ပြန်တာပါ။ Stack ပေါ်က Data ကို ယူပြီး Return မပြန်ပါဘူး။ Return opcode က RETURN ဖြစ်ပါတယ်။
  • Example:
PUSH1 0x07 
PUSH1 0x50
MSTORE

PUSH1 0x20
PUSH1 0x50
RETURN

Longer data lifespans (ပိုကြာသော data ရဲ့အသက်စက်ဝန်း)

  • အကယ်လို့ တူညီတဲ့ Data တစ်ခုတည်းကိုပဲ Opcode တွေအများကြီးကနေ ထပ်ခါထပ်ခါ ပြင်နေရပြီဆိုရင် (သို့) Internal function call လိုမျိုး function call တွေခေါ်ရင် Memory ကဒီနေရာမှာ များစွာအသုံးဝင်ပါတယ်။
  • Data ကို Memory ပေါ်မှာသိမ်းခြင်းအားဖြင့် Opcode ကပိုပြီး ရိုးရှင်းလာမှာဖြစ်ပါတယ်။ အထူးသဖြင့် Stack operation တွေဖြစ်တဲ့16 swap/dup reach limitation လိုဟာတွေလုပ်ရင် ပိုသတိထားရပါတယ်။

Larger data (ကြီးမားတဲ့ data တွေကိုသိမ်းထားနိုင်ခြင်း)

  • တချို့ Stack ပေါ်မှာမဆန့်တဲ့ Data တွေ (အထူးသဖြင့် word size 32 bytes နဲ့ မလောက်တဲ့ Operation) တွေမှာ Memory ကအထူးသင့်တော်ပါတယ်။

2.2 Solidity ကလုပ်ထားတဲ့ Reserved Spaces

Solidity က ပထမ 4 x 32 bytes ကို Reserved လုပ်ထားပါတယ်။

  • 0x00 - 0x3f (64 bytes): scratch space for hashing methods
  • 0x40 - 0x5f (32 bytes): currently allocated memory size (aka. free memory pointer)
  • 0x60 - 0x7f (32 bytes): zero slot used as the initial value for empty dynamic memory arrays

2.3 Free Memory Pointer (FMP)

Free Memory Pointer လို့ခေါ်တဲ့ Memory address 0x40 မှာရှိတဲ့ Value က နောက်မသုံးရသေးတဲ့ Memory ရဲ့ Location ဖြစ်ပါတယ်။ Data စပြီး ရိုက်လို့ရမယ့် Memory address ပေါ့။ ဒီလိုလုပ်ခြင်းအားဖြင့် data override လုပ်တဲ့ကိစ္စတွေကို ရှောင်ရှားနိုင်ပါတယ်။

2.3 Memory ပေါ်မှာသိမ်းတဲ့ Default data & value တွေ

  • Complex Type Function Arguments တွေ
  • Complex Type Local variables တွေ
  • Function တွေကနေ Return ပြန်လိုက်တဲ့ value တွေ

Return ပြန်တာ Complex type or normal type ဘာလာလာ memory ပေါ်မှာသိမ်းတယ်။ RETURN ဆိုတဲ့ opcode ကိုသုံးတယ်။

Complex value type ကို function ကနေ Return ပြန်ရင် memory ဆိုတဲ့ Keyword ကိုသုံးကိုသုံးပေးရတယ်။

2.4 Memory ပေါ်မှာ Data ကိုဖတ်ခြင်း (MLOAD)

Solidity ရဲ့ Documentation ထဲမှာ အောက်ပါအတိုင်းဖော်ပြထားပါတယ်

…reads are limited to a width of 256 bits, while writes can be either 8 bits or 256 bits wide.

Memory ပေါ်ကို data ရိုက်တာက တစ်ခုရိုက်ရင် 256-bits အတိအကျ Limited လုပ်ထားသော်လည်း data ကို read တဲ့အခါ 8-bits သို့ 256-bits ဖတ်လို့ရပါတယ်။

Yellow paper မှာပါတဲ့ MLOAD ဆိုတဲ့ Keyword ကိုဖတ်ကြည့်ရအောင်။

mload opcode from yellow paper

µ’s[0] = Stack ရဲ့ offset [0] (ရလာတဲ့ value ကို အပေါ်ဆုံးမှာသိမ်းမယ်)
µm[] = Content in memory starting at a specific offset
µs[0] = Stack ပေါ်ကပထမဆုံး item.

µ's[0] ≡ µm[µs[0] . . .(µs[0]+31)]

အပေါ်က Formula ရဲ့ Meaning က Memory content ရဲ့ Offset µs[0] ကနေနောက် 31 bytes ကိုယူပြီး µ’s[0] မှာသိမ်းမယ်လို့ပြောတာဖြစ်ပါတယ်။

2.5 Memory Expansion

ဆိုပါစို့ Memory n လို့ references လုပ်တိုင်း EVM က မင်းကို Allocate မလုပ်ရသေးတဲ့ Memory 0 ကနေ n ထိ allocate လုပ်ပေးမှာဖြစ်ပါတယ်။ အဲလိုလုပ်တဲ့အခါ ကိုယ်တခါမှ မယူရသေးတဲ့ Offset ကိုယူမိရင် memory expansion ဆိုတာဖြစ်လာမှာဖြစ်ပါတယ်။

ဆိုတော့ ပထမဆုံးအကြိမ် memory allocate လုပ်ရင် memory expansion ဖြစ်မှာဖြစ်ပါတယ်။ နောက်အကြိမ်တွေမှာရှိပြီးသားကိုသုံးပီး အဲထပ်ပိုသုံးမိရင် Memory expansion ထပ်ဖြစ်မှာဖြစ်ပါတယ်။

လက်ရှိ Memory ကို access လုပ်ဖူးတဲ့ အမြင့်ဆုံး Offset ကို MSIZE ဆိုတဲ့ Opcode ကိုသုံးပြီးကြည့်လို့ရပါတယ် The size will always be a multiple of word (32 bytes).

Solidity မှာ “how many bytes are stored in memory” နဲ့ “the largest index/offset accessed in memory” ရဲ့ differences ကမတူပါဘူး။

ပိုပြီးမြင်သာအောင် အောက်ပါ Example များကိုကြည့်ပါ။

pragma solidity ^0.8.0;

contract TestingMsize {
function test()
public
pure
returns (
uint256 freeMemBefore,
uint256 freeMemAfter,
uint256 memorySize
)
{
// before allocating new memory
assembly {
freeMemBefore := mload(0x40)
}
bytes memory data = hex"cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe";
// after allocating new memory
assembly {
// freeMemAfter = freeMemBefore + 32 bytes for length of data + data value (32 bytes long)
// = 128 (0x80) + 32 (0x20) + 32 (0x20) = 0xc0
freeMemAfter := mload(0x40)
// now we try to access something further in memory than the new free memory pointer :)
let whatIsInThere := mload(freeMemAfter)
// now msize will return 224.
memorySize := msize()
}
}
}
//Ref: <https://gist.github.com/CJ42/cadf126bdb839e2432db812da75ba73f#file-testingmsize-sol>

အပေါ်က Code ကို Plain Burmese နဲ့ရှင်းပြရရင်,

  • ပထမဆုံး FMP (0x40) မှာရှိတဲ့ 0x80(= 128) (စယူလို့ရတဲ့ memory address) ကိုယူလိုက်တယ်။
  • 64 bytes ရှိတဲ့ data ကို Memory ပေါ်မှာသိမ်းတယ်
  • ဒုတိယအကြိမ် FMP(0x40) မှာရှိတဲ့ pointer ကိုခေါ်ကြည့်တယ်။ EVM က 0xc0(= 192) ပြန်တယ်။
  • memory address 0xc0 မှာဘာရှိလည်း Check တယ်။
  • MSIZE ခေါ်ကြည့်တယ်။ 224 ဆိုပြီး Return ပြန်တယ်။

တကယ်တန်း Memory ပေါ်မှာယူသွားတဲ့ size က 64 bytes, from 0x80 to 0xc0 .

MSIZE ကပြန်တာက 224 အဲတော့ ဘာလွဲနေတာလဲ။

တကယ်ရောက်ရမှာက memory address in decimals 192. mszie ကပြန်တာက 224.

224 = 192 + 32 ဆိုတော့ တကယ်သိမ်းတာထက် 32 bytes ပိုပြီး allocate လုပ်ထားတာကိုတွေ့ရမှာပါ။

32 bytes 
x 4 (the first 4 reserved spaces in memory)
---------------------
= 128
+ 64 bytes (the variable `data`)
---------------------
= 192 (total)

အပေါ်ကအဖြစ်အပျက်ကို Memory expansion ဖြစ်တယ်လို့ခေါ်ပါတယ်။

2.6 Memory Expansion Gas cost Formula

expansion_cost(bytes) = words^2 / 512 + words * 3
where
words = (bytes + 31) / 32

အထက်ပါ Formula က ဘယ်လောက် allocate ယူသွားမယ်ဆိုတာတွက်တာမဟုတ်ပါဘူး။ ကိုယ်ပေးလိုက်တဲ့ Bytes ကိုတွက်ပီး Cost ဘယ်လောက်ကုန်လဲတွက်တာဖြစ်ပါတယ်။

အဲအတွက် တိကျတဲ့အဖြေရဖို့ 2 ခါ တွက်ပေးရပါမယ်။

real_cost(current, prev) = expansion_cost(current) - expansion_cost(prev)

2.7 Example#1 Memory Expansion Calculation

PUSH1 0x01
PUSH1 0x40
MSTORE

အပေါ်က MSTORE opcode ကို run ရင် EVM က total 96 bytes allocate လုပ်သွားမှာဖြစ်ပါတယ်။ တကယ်သိမ်းချင်တာက 0x01 only 32-bytes ပဲဖြစ်ပါတယ်။

အဲတော့ value 0x01 ကို memory address 0x40 ကနေ 0x5f အထိ သိမ်းသွားလိမ့်မယ် total 32 bytes.

ဒီ example နဲ့မဆိုင်ပေမယ့် Solidity က Default သိမ်းတဲ့ words size 4 ခုရှိတာကို သတိရပါ။ ဒီ Example မှာ အဲ ၄ ခုကိုခဏမေ့ထားပါတယ်။

Memory address 0x00 ကနေ 0x5f က total 96 bytes ရှိပါတယ်။ ဒါကြောင့် ကိုယ်သိမ်းတဲ့ address က 0x40 မှာသိမ်းတာဖြစ်ပေမယ့် 0x00 - (0x40 + 32 bytes) = 0x00 to 0x5f အထိတွက်ပါတယ်

//Forumla
expansion_cost(bytes) = words^2 / 512 + words * 3
where
words = (bytes + 31) / 32
expansion_cost(96) = 3^2 / 512 + 3 * 3 = 9
where
words = (96 + 31) / 32 = 2.968

အထက်ပါ example ရဲ့ memory expansion cost က expansion_cost(96) = 3^2 / 512 + 3 * 3 = 9 gas ဖြစ်ပါတယ်။ Memory ပေါ်မှာယူသွားတာက word size 3 ဖြစ်ပါတယ်။ 3 x 32 = 96 bytes allocated

2.8 Example#2 Memory Expansion Calculation

PUSH1 0x01
PUSH1 0x20
MSTORE

PUSH1 0x02
PUSH1 0x40
MSTORE

ဒီ example မှာ MSTORE ၂ ခါလုပ်သွားပါတယ်။

  • ပထမ mstore memory address 0x20 မှာ value 0x01 ကိုသိမ်းတယ်။
  • ဒုတိယ mstore မှာက memory address 0x40 မှာ value 0x02 ကိုသိမ်းတယ်။

ပထမတကြိမ်သိမ်းတဲ့ absolute memory address က 0x20 to 0x3f ဖြစ်ပါတယ်။ Total memory size က 0x00 to 0x3f ဖြစ်တဲ့အတွက် size က 64 bytes ပါ။

expansion_cost(bytes) = words^2 / 512 + words * 3
where
words = (bytes + 31) / 32
mstore1_expansion_cost(64) = 3^2 / 512 + 3 * 3 = 9 gas
where
words = (64 + 31) / 32 = 2.96875

ဒုတိယအကြိမ် သိမ်းတာက absolute memory address က 0x40 to 0x5f အထိဖြစ်ပါတယ်။ Total memory size က 0x00 to 0x5f ဖြစ်တဲ့အတွက် size က 96 bytes ပါ။

ဒါပေမယ့် ဒုတိယ အကြိမ် အတွက် 96 bytes ယူသွားတယ်ပြောလို့မရပါဘူး။ MSTORE ပထမတစ်ခါလုပ်တုန်းကဟာကို နှုတ်ရပါ့မယ်။

3 word size - 2 word size = 1 word size 32 bytes ပဲ ယူသွားတာဖြစ်ပါတယ်။ Expansion cost ကိုတွက်မယ်ဆိုရင် ဒုတိယတခါ mstore အတွက် 5 gas ပေးရမှာဖြစ်ပါတယ်။ ပထမတစ်ခု mstore အတွက်က 9 gas ပေးရမှာဖြစ်ပါတယ်။

mstore2_expansion_cost(32) = 2^2 / 512 + 2 * 3 = 6 gas
where
words = (32 + 31) / 32 = 1.96875

တွေ့ရတဲ့အတိုင်း Memory expansion gas cost က growth ဖြစ်နေတာကိုတွေ့ရမှာပါ။ Solidity ရဲ့ doc အရ -

The memory expansion cost increases in the following way: linearly for the first 724 bytes, quadratically after that

2.9 Example#3 Memory Expansion Calculation

MSIZE // Initially 0
PUSH1 0
MLOAD // Read first word
POP
MSIZE // Now size is 1 word
PUSH1 0x39
MLOAD // Read part of third word
POP
MSIZE // Now size is 3 words

ဒီတစ်ခါက read operation ပါ။ Read operation လည်း ထိုနည်းအတူ gas expansion ရှိတာကိုတွေ့နိုင်ပါတယ်။ memory address 0x00 နဲ့ 0x39 ကိုဖတ်တာဖြစ်ပါတယ်။ 0x00 to 0x39 က 58 bytes ရှိပါတယ်။

word size: words = (58 + 31) / 32 = 2.75 (3 word size which is 3*32 = 96 bytes).

အဲတာကြောင့် MSIZE ခေါ်တဲ့အခါ size က 3 ဖြစ်သွားမှာဖြစ်ပါတယ်။

ဒါဆို example 2.5 ကအဖြေ 224 ဖြစ်ရသလဲဆိုတာကို ပြန်တွက်လို့ရမယ်ထင်ပါတယ်။

>>> hex(0xc0 + 31)
'0xdf'
>>> 0xdf/32
6.96875
>>> 7 * 32
224

2.10 Memory ကိုသုံးတဲ့ Opcode များ

Opcodes that write to memory:

  • MSTORE / MSTORE8 – Writes data from the stack into memory.
  • CALLDATACOPY – Copies data from calldata into memory.
  • CODECOPY – Copies bytecode from the currently running contract into memory.
  • EXTCODECOPY – Copies bytecode from an external contract address into memory.
  • RETURNDATACOPY – Copies data returned from the last external call into memory.

Opcodes that read from memory:

  • MLOAD – Copies 32 bytes of memory onto the stack.
  • RETURN – Returns a specified slice of memory.
  • SHA3 – Performs a keccak hash of a specified slice of memory, then puts the result onto the stack.
  • LOG0 - LOG4 – Reads a specified slice of memory and emits it as part of an event.
  • REVERT – Reverts with a specified slice of memory as the revert message.
  • CREATE / CREATE2 – Creates a new contract with a specified slice of memory as the new contract’s deployment bytecode.

Opcodes that both read and write:

  • CALL / STATICCALL / DELEGATECALL – Reads a slice of memory as calldata, calls an external contract, then writes the returned data to a slice of memory.

3. Advance Memory layout in references type and function call.

3.1 FMP in Assembly

အပေါ်မှာပြောခဲ့တဲ့အတိုင်း FMP (Free memory pointer) ဆိုတာက access မလုပ်ရသေးတဲ့ Memory area ရဲ့ offset ဖြစ်ပါတယ်။

Solidity ရဲ့ doc အရ FMP ကို memory address 0x40 မှာသိမ်းတယ်။ 32 bytes (one word) နေရာယူပါတယ်။

High level language တွေဖြစ်တဲ့ Solidity, Vyper programming language တွေကိုသုံးရင် ကိုယ်တိုင် FMP ကို Manage လုပ်ပေးစရာမလိုပါဘူး။ ဒါပေမယ့် inline assembly သုံးတာဖြစ်ဖြစ် Assembly ကိုတိုက်ရိုက်ရေးတာပဲဖြစ်ဖြစ် FMP ကို manage လုပ်ပေးဖို့ သတိထားရပါမယ်။

မဟုတ်ရင် memory မှာရှိနေတဲ့ Data ကို Override ဖြစ်သွားမှာဖြစ်ပါတယ်။

FMP ကို update လုပ်တဲ့ Process က ၂ ခုရှိပါတယ်။

  • Fetch: FMP မှာရှိတဲ့ address ကို fetch လုပ်မယ် (Stack ပေါ်ကိုတင်မယ်)
  • Update: ပီးရင် ကိုယ် allocate လုပ်ချင်တဲ့ data size ကိုပေါင်းမယ်။ ရလာတဲ့ result ကို memory address 0x40 မှာသိမ်းမယ်။
freeMemoryPointer + dataSizeBytes = newFreeMemoryPointer

3.1.1 Assembly အနေနဲ့ FMP ကိုဘယ်လို Manage လုပ်လဲ?

အောက်ပါ code ကို Compile လုပ်ရင် byte code ရလာမှာဖြစ်ပါတယ်။

compile source file to get binary output.
608060405234801561000f575f80fd5b506101068061001d5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063d275ca7214602a575b5f80fd5b60306032565b005b6038605f565b603e6081565b60015f1b815f60028110605257605160a3565b5b6020020181815250505050565b6040518060a00160405280600590602082028036833780820191505090505090565b6040518060400160405280600290602082028036833780820191505090505090565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffdfea264697066735822122081f618c4afa0e703f12e6d7d113da7eb2b9a1975d5c8297bcb79f35ea084eae964736f6c63430008150033

အ‌ပေါ်က 6080604052 ဆိုတဲ့ Byte code ကိုရှင်းပြပေးပါ့မယ်။ အဲ code က FMP ကို allocate လုပ်တဲ့ code ပါ။

Byte code 60 ဆိုတာက PUSH1 = Push 1 byte on stack လို့ အဓိပ္ပာယ်ရပါတယ်။

Byte code 52 ဆိုတာက MSTORE = store item on stack to memory ပါ။

60 80                       =   PUSH1 0x80
60 40 = PUSH1 0x40
52 = MSTORE

အပေါ်က code အဓိပ္ပာယ်က mstore(0x40, 0x80) ဖြစ်ပါတယ်။ Memory address 0x40 မှာ 0x80 ကို သွားသိမ်းပါဆိုပြီးဖြစ်ပါတယ်။

Solidity’s memory layout reserves four 32-byte slots:

  • 0x00 - 0x3f (64 bytes): scratch space
  • 0x40 - 0x5f (32 bytes): free memory pointer
  • 0x60 - 0x7f (32 bytes): zero slot

bytes32[5] memory a ဆိုတဲ့ code ကို Highlight လုပ်ပြထားပါတယ်။ Compiler က 32 bytes size အခန်း ၅ ခန်းကို သွားပြီး allocate လုပ်တာဖြစ်ပါတယ်။

Remember elements in memory arrays in Solidity always occupy multiples of 32 bytes (this is even true for bytes1[], but not for bytes and string)

// load free memory pointer
// memory address 0x40 မှာရှိတဲ့ value ကို stack ပေါ်ကိုတင်တယ်။
PUSH1 0x40
MLOAD

// duplicate free memory pointer
// 0x40 မှာရှိတဲ့ value ကို stack ပေါ်မှာ Duplicate လုပ်တယ်။
DUP1
// 0xa0 = 160 in decimal, 32 * 5 = 160 first array is length 5
PUSH1 0xa0
// new fmp ကိုတွက်တယ်
// free memory pointer (0x80) + space for array (0xa0) = new free memory pointer
ADD
// Save this new value 0x120 to the free memory location
PUSH1 0x40
MSTORE

Memory ပေါ်မှာ variable a အတွက် နေရာ allocate လုပ်တာပြီးပါပြီ။ (Allocate လုပ်တာနဲ့ data initialize လုပ်တာ မတူပါဘူး။ Allocate လုပ်တာက နေရာကြုိယူထားတဲ့သဘောပါ၊ တကယ့် Data ကိုမသိမ်းရသေးပါဘူး )

3.2 FMP in solidity

Solidity မှာတော့ assembly လိုမျိုး FMP ကို manually လုပ်ပေးစရာမလိုပါဘူး။ ဥပမာ bytes memory myVariable လို့ ရေးလိုက်တာနဲ့ Compiler က memory ပေါ်မှာ myVariable အတွက် Memory allocate လုပ်ပေးသွားမှာဖြစ်ပါတယ်။

function test() public 
{
string memory myVariable = "Hello World";
}

အပေါ်က Code ကို opcodes အဖြစ် solc ကိုသုံးပြီး Compile ရင် အောက်ပါအတိုင်းရလာမှာဖြစ်ပါတယ်။

Function Test နဲ့ဆိုင်တဲ့ Opcode ကအောက်မှာဖြစ်ပါတယ်။ ဒီ article က memory နဲ့ဆိုင်တာမို့ Memory ကိုပဲအဓိကထားပြီး ပြောသွားပါမယ်။ Opcode နဲ့ဆိုင်တဲ့အပိုင်းကို သပ်သပ် နောက် Article နဲ့ရေးပေးပါ့မယ်။

Line no 3–9 ကိုကြည့်ပါ Memory ပေါ်မှာ “Hello World” ဆိုတဲ့ string ကိုမရိုက်ခင် 0x40 (64 in decimals) ကို FMP pointer မှာရှီတဲ့ value နဲ့ ပေါင်းပြီး Update လုပ်ပေးသွားတာမြင်ရမှာပါ။ တကယ်ယူတာတာ 11 bytes ပေမယ့် 64 bytes ကို allocate လုပ်သွားတာဖြစ်ပါတယ်။

Memory ပေါ်မှာရှိတဲ့ string က Length + String itself တွဲပြီးသိမ်းတာကိုလည်းမြင်ရမှာပါ။

3.3 Memory Ref as function parameters

စာဖတ်သူအနေနဲ့ Functional Signature ကိုသိပြီးသားလို့သတ်မှတ်ပြီး အောက်ကစာကိုရေးပါတယ်။

Complex data type တွေကို function to function pass လုပ်ရင်ပဲဖြစ်ဖြစ် အပြင်က call ကိုလက်ခံရင်ပဲဖြစ်ဖြစ် memory ဆိုတဲ့ keyword ကိုသုံးရပါတယ်။

ဒီလိုမျိုး Function call parameters မှာ memory သာပါလာခဲ့ရင် EVM က အောက်ပါ အတိုင်းအစဥ် လိုက် အလုပ်လုပ်ပါတယ်။

  • Call data က String offset ကို stack ပေါ်ကိုတင်ပါတယ်။
  • Call data ထဲမှာရှိတဲ့ string ရဲ့ length ကို ခုနက offset ကိုသုံးပြီးတွက်တယ်။ ပြီးရင် Stack ပေါ်ကိုတင်ပါတယ်။
  • Call data က String ကို memory ပေါ်ကိုတင်ဖို့ memory မှာ allocate လုပ်ပါတယ်။
  • Call data က String ကို (offset to length အထိ) ကို memory ပေါ်ကိုဆွဲတင်ပါတယ်။

Call data ကို Memory ထဲကို copy ဖို့ကို CALLDATACOPY ဆိုတဲ့ opcode ကိုသုံးပါတယ်။ ဒီ Opcode က parameters ၃ ခုလက်ခံပါတယ် (Stack ရဲ့ ထိပ်ဆုံးမှာ ရှိတဲ့ value 3 ခုကို ဆိုလိုတာပါ)

  • Destination (offset) in memory.
  • The source (offset) in calldata.
  • the number of bytes want to copy.

ဒီ Article နဲ့ cover မဖြစ်လိုက်တာ အများကြီးကျန်ခဲ့ပါတယ်။

Function call to call, Contract to contract မှာ Memory ဘယ်လိုအလုပ်လုပ်သွားသလဲ။ Memory ပေါ်မှာ Array ကို by index နဲ့ဘယ်လိုသိမ်းသွားလဲ စတာတွေ ကျန်ပါသေးတယ်။ Opcode article မှာထည့်ရေးမယ်လို့စိတ်ကူးပြီး ဒီ Article ကို ဒီမှာပဲနားလိုက်ပါတယ်ခင်ဗျာ။

Ref:

https://github.com/CJ42/All-About-Solidity/

Deconstructing a Solidity Contract — Part II: Creation vs. Runtime — OpenZeppelin blog

https://ethereum.github.io/yellowpaper/paper.pdf

--

--