Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #6: Manajemen Memory Yul — mstore, mload, dan Free Memory Pointer

OS
Open Soft Team

Engineering Team

Memory di EVM: Model Fundamental

Memory EVM adalah array byte linear yang dimulai dari nol dan tumbuh sesuai kebutuhan. Tidak seperti storage yang persisten, memory hanya ada selama panggilan fungsi saat ini — ia dibersihkan sepenuhnya ketika eksekusi selesai atau sub-panggilan dimulai.

Dua opcode fundamental untuk memory:

  • MSTORE(offset, value) — Menulis 32 byte di offset yang diberikan
  • MLOAD(offset) — Membaca 32 byte dari offset yang diberikan
assembly {
    mstore(0x00, 0x42)   // Tulis 0x42 di memory[0x00..0x1f]
    let val := mload(0x00) // Baca 32 byte dari memory[0x00]
    // val = 0x42
}

MSTORE8: Menulis Satu Byte

assembly {
    mstore8(0x00, 0xff)  // Tulis 1 byte: memory[0x00] = 0xff
    // Byte lain di word ini tidak terpengaruh
}

Free Memory Pointer Solidity

Solidity menyimpan pointer ke memory bebas di offset 0x40. Setiap alokasi memory memperbarui pointer ini:

assembly {
    let fmp := mload(0x40)  // Baca pointer saat ini
    
    // Alokasi 64 byte
    let ptr := fmp
    mstore(0x40, add(fmp, 0x40))  // Update pointer
    
    // Gunakan ptr..ptr+64
    mstore(ptr, 0x01)       // Tulis di blok yang dialokasi
    mstore(add(ptr, 0x20), 0x02)
}

PENTING: Jika Anda menggunakan inline assembly dan mengabaikan free memory pointer, Anda bisa menimpa data yang dialokasi oleh Solidity. Ini menyebabkan bug yang sangat sulit di-debug.

Tata Letak Memory Solidity

Offset     Ukuran   Tujuan
0x00-0x1f  32 byte  Scratch space 1 (untuk hashing)
0x20-0x3f  32 byte  Scratch space 2 (untuk hashing)
0x40-0x5f  32 byte  Free memory pointer
0x60-0x7f  32 byte  Slot zero (selalu 0)
0x80+      dinamis  Area alokasi

Scratch space (0x00-0x3f) aman untuk digunakan dalam assembly karena Solidity hanya menggunakannya sementara untuk operasi keccak256.

Pola Memory Umum

Pola 1: Hashing Dua Nilai

assembly {
    // keccak256(abi.encodePacked(a, b))
    mstore(0x00, a)
    mstore(0x20, b)
    let hash := keccak256(0x00, 0x40)  // Hash 64 byte
}

Ini adalah cara paling efisien untuk hashing — menggunakan scratch space tanpa alokasi memory.

Pola 2: Encoding ABI Manual

assembly {
    let ptr := mload(0x40)  // Alokasi dari free memory pointer
    
    // Encode: transfer(address,uint256)
    mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
    mstore(add(ptr, 0x04), to)      // address (padded ke 32 byte)
    mstore(add(ptr, 0x24), amount)  // uint256
    
    // Call
    let ok := call(gas(), token, 0, ptr, 0x44, 0x00, 0x20)
}

Pola 3: Membaca Return Data

assembly {
    // Setelah call, baca return data
    let size := returndatasize()
    let ptr := mload(0x40)
    returndatacopy(ptr, 0, size)  // Salin return data ke memory
    
    // Parse hasil
    let success := mload(ptr)  // Asumsi: mengembalikan bool
}

Biaya Ekspansi Memory

Memory tidak gratis — biayanya tumbuh secara kuadratik:

biaya(ukuran_word) = 3 * ukuran_word + floor(ukuran_word^2 / 512)
Memory DigunakanBiaya TotalBiaya Marginal
32 byte (1 word)3 gas3 gas
1 KB (32 words)98 gas3 gas/word
10 KB (320 words)1160 gas4+ gas/word
100 KB (3200 words)29600 gas16+ gas/word
1 MB (32000 words)2048000+ gasmahal

Biaya kuadratik ini berarti Anda harus sangat berhati-hati dengan penggunaan memory. Jangan pernah mengalokasi lebih dari yang diperlukan.

Teknik Optimasi Memory

1. Gunakan Kembali Memory

Daripada terus mengalokasi, tulis ulang area memory yang sama:

assembly {
    // Daripada alokasi baru untuk setiap operasi:
    let scratch := 0x00  // Gunakan scratch space
    
    // Operasi 1
    mstore(scratch, data1)
    let hash1 := keccak256(scratch, 0x20)
    
    // Operasi 2 — tulis ulang area yang sama
    mstore(scratch, data2)
    let hash2 := keccak256(scratch, 0x20)
}

2. Hindari Free Memory Pointer untuk Operasi Sementara

assembly {
    // Buruk: alokasi melalui FMP untuk data sementara
    let ptr := mload(0x40)
    mstore(0x40, add(ptr, 0x20))
    mstore(ptr, someValue)
    // Memory ini tidak pernah dibebaskan
    
    // Baik: gunakan scratch space
    mstore(0x00, someValue)
    // Tidak ada alokasi permanen
}

3. Batch Memory Write

assembly {
    // Tulis beberapa nilai secara berurutan
    let base := mload(0x40)
    mstore(base, val1)
    mstore(add(base, 0x20), val2)
    mstore(add(base, 0x40), val3)
    mstore(add(base, 0x60), val4)
    // Satu update FMP
    mstore(0x40, add(base, 0x80))
}

MCOPY (EIP-5656)

Diperkenalkan di upgrade Cancun (2024), MCOPY menyalin byte dalam memory:

assembly {
    mcopy(dst, src, length)
}

Biaya: 3 gas + 3 gas per word yang disalin + biaya ekspansi. Ini lebih murah daripada loop MLOAD/MSTORE manual untuk blok data besar.

Memory dalam Konteks Sub-panggilan

Ketika kontrak memanggil kontrak lain (CALL, STATICCALL, DELEGATECALL), kontrak yang dipanggil mendapatkan memory baru yang kosong. Memory pemanggil dipertahankan tetapi tidak bisa diakses oleh yang dipanggil.

Data dikomunikasikan antara pemanggil dan yang dipanggil melalui:

  • Calldata (input): Pemanggil menulis ke memory, meneruskan via parameter offset/length ke CALL
  • Return data (output): Yang dipanggil menulis ke memory-nya, pemanggil membaca via RETURNDATACOPY

Kesimpulan

Manajemen memory yang efisien adalah keterampilan kritis untuk pengembang Yul. Pahami tata letak memory Solidity, gunakan scratch space untuk operasi sementara, waspadai biaya ekspansi kuadratik, dan selalu pertahankan konsistensi free memory pointer saat mencampur Solidity dan inline assembly.