LEA sebagai kalkulator alamat

Mekanisme alamat efektif basis ditambah indeks dikalikan skala ditambah displacement dan penggunaannya sebagai aritmetika.

Daftar isi

Topik ini membahas LEA, instruksi yang menghitung alamat efektif dan meletakkan hasilnya di register tanpa membaca memori. Komponen alamat efektif diuraikan satu per satu dan dibuktikan pada penghitungan alamat elemen array, lalu LEA digunakan sebagai aritmetika yang murah. Pada akhir topik, perbedaan LEA terhadap MOV dan MUL dapat dijelaskan dari perilakunya sendiri.

Komponen alamat efektif#

Alamat efektif (effective address) adalah nilai alamat yang dihitung dari beberapa komponen sebelum benar-benar digunakan untuk menyentuh memori. Pada mode 64-bit, bentuk lengkapnya dirangkum oleh persamaan berikut.

alamat=basis+indeks×skala+displacement \text{alamat} = \text{basis} + \text{indeks} \times \text{skala} + \text{displacement}

Komponen basis dan indeks berupa register 64-bit, skala hanya boleh bernilai 1, 2, 4, atau 8, sedangkan displacement berupa konstanta bertanda yang tertulis langsung di dalam instruksi. Tidak semua komponen wajib ada, sehingga [rdi], [rdi + rsi*4], dan [rdi + rsi*4 + 8] semuanya merupakan alamat efektif yang sah.

KomponenIsinyaContoh
basissatu register 64-bitrdi
indekssatu register 64-bit yang dikalikan skalarsi*4
skalakonstanta 1, 2, 4, atau 84
displacementkonstanta bertanda8

Skala 1 tetap sah dan lazim ditulis eksplisit, misalnya [rdi + rsi*1], meskipun hasilnya sama dengan [rdi + rsi]. Displacement juga boleh negatif, sehingga [rdi - 8] merujuk 8 byte sebelum alamat yang ditunjuk rdi. Kombinasi lengkap inilah yang membuat satu bentuk pengalamatan mampu menjangkau elemen array, field struct, dan variabel lokal tanpa perhitungan tambahan.

Contoh beranotasi berikut memperlihatkan posisi setiap komponen pada satu instruksi.

nasm Anatomi satu alamat efektif
lea  rax, [rdi + rsi*4 + 8]
;          |      |     |
;          |      |     +-- displacement berupa 8
;          |      +-------- indeks rsi dengan skala 4
;          +--------------- basis rdi

Hasil perhitungan rdi + rsi*4 + 8 ditaruh di rax. LEA dipilih karena hasil yang dibutuhkan memang berupa alamat, tetapi perhatikan bahwa tidak ada satu byte pun yang dibaca dari memori untuk menghasilkannya.

LEA tidak menyentuh memori#

Perbedaan LEA dengan MOV terletak pada langkah terakhirnya. MOV menghitung alamat efektif, lalu membaca atau menulis memori pada alamat tersebut. LEA berhenti pada langkah perhitungan dan menaruh angka alamatnya di register tujuan, tanpa akses memori dan tanpa memperbarui flag. Instruksi ini tepat digunakan ketika alamat elemen array dibutuhkan sebagai nilai, misalnya untuk disimpan sebagai pointer atau diteruskan ke fungsi.

Program berikut mengisi elemen indeks 5 dari sebuah array berisi delapan bilangan 32-bit. Alamat dasar array dihitung dengan lea rdi, [suhu], sedangkan alamat elemen dihitung dengan lea rax, [rdi + rsi*4] karena setiap elemen berjarak 4 byte.

nasm lea-array.asm
default rel

section .data
suhu: times 8 dd 0        ; delapan bilangan 32-bit, tiap elemen 4 byte

section .text
global _start

_start:
    lea  rdi, [suhu]           ; alamat dasar array
    mov  rsi, 5                ; indeks elemen
    lea  rax, [rdi + rsi*4]    ; alamat elemen indeks 5
    mov  dword [rax], 42       ; tulis melalui alamat hasil LEA
    mov  eax, [rax]            ; baca kembali dari alamat yang sama
    mov  edi, eax
    mov  eax, 60
    syscall
bash
# rakit dan tautkan program
nasm -f elf64 lea-array.asm -o lea-array.o
ld lea-array.o -o lea-array

# alamat elemen indeks 5 pada rax, lalu isi array diperiksa
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi 4' -ex 'info registers rax' -ex 'x/8xw &suhu' ./lea-array
text Keluaran
Breakpoint 1 at 0x401000

Breakpoint 1, 0x0000000000401000 in _start ()
0x0000000000401016 in _start ()
rax            0x402014            4202516
0x402000:	0x00000000	0x00000000	0x00000000	0x00000000
0x402010:	0x00000000	0x0000002a	0x00000000	0x00000000

Register rax menampung 0x402014, yaitu alamat dasar 0x402000 ditambah 5 dikalikan 4 byte. Pemeriksaan delapan kata memori menampilkan 0x2a tepat pada elemen indeks 5, sedangkan elemen lain tetap nol. Penulisan dan pembacaan kembali dilakukan melalui alamat hasil LEA, sehingga seluruh jalur penghitungan alamat terbukti bekerja dari awal sampai akhir.

LEA sebagai aritmetika#

Karena LEA hanya menjumlahkan dan mengalikan dengan skala, instruksi ini dapat digunakan sebagai aritmetika bilangan bulat yang murah. Bentuk [rdi + rdi*8] menghasilkan sembilan kali nilai rdi karena basis dan indeks menggunakan register yang sama. Untuk sepuluh kali nilai, satu LEA menghasilkan lima kali nilai melalui [rdi + rdi*4], lalu satu add menggandakannya. Perlu diingat bahwa skala hanya menyediakan faktor 1, 2, 4, dan 8, sehingga perkalian tiga kali dinyatakan sebagai [rdi + rdi*2].

Program berikut menjalankan kedua bentuk di atas dengan rdi berisi 10, lalu hasilnya diperiksa lewat gdb pada dua titik berbeda.

nasm lea-demo.asm
default rel

section .text
global _start

_start:
    mov  rdi, 10                 ; nilai untuk kedua percobaan
    lea  rax, [rdi + rdi*8]      ; 9 * 10
    mov  rbx, rax
    lea  rax, [rdi + rdi*4]      ; 5 * 10
    add  rax, rax                ; 10 * 10
    mov  edi, 0
    mov  eax, 60
    syscall
bash
# rakit dan tautkan program
nasm -f elf64 lea-demo.asm -o lea-demo.o
ld lea-demo.o -o lea-demo

# hasil perhitungan 9 * rdi pada rax dan rbx
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi 3' -ex 'info registers rax rbx' ./lea-demo

# hasil perhitungan 10 * rdi pada rax setelah add
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi 5' -ex 'info registers rax' ./lea-demo
text Keluaran
Breakpoint 1 at 0x401000

Breakpoint 1, 0x0000000000401000 in _start ()
0x000000000040100c in _start ()
rax            0x5a                90
rbx            0x5a                90
Breakpoint 1 at 0x401000

Breakpoint 1, 0x0000000000401000 in _start ()
0x0000000000401013 in _start ()
rax            0x64                100

Keluaran pertama menampilkan 0x5a atau 90 pada rax dan rbx, persis sembilan kali nilai awal. Keluaran kedua menampilkan 0x64 atau 100 setelah add menggandakan hasil LEA yang bernilai 50. Tidak ada instruksi perkalian yang digunakan di sepanjang program, dan nilai 10 hanya dimuat satu kali di awal.

Sebagai pembanding, MUL mengharuskan satu operannya berada di rax secara implisit, menyimpan hasil 128-bit pada pasangan rdx dan rax, serta memperbarui flag. LEA tidak menyentuh flag sama sekali, dan pada banyak prosesor modern dieksekusi oleh unit penghitung alamat dalam satu siklus. Sifat itulah yang membuat compiler memilih LEA untuk perkalian dengan faktor kecil yang berpola basis ditambah indeks berskala.

Kesalahan umum#

Mengharapkan LEA membaca isi memori. Hasil lea rax, [rdi + rsi*4] berupa alamatnya, bukan isinya, sehingga program yang mencari nilai sebuah elemen harus dilanjutkan dengan MOV dari alamat tersebut. Pola LEA diikuti MOV memang terdiri atas dua instruksi, dan pemisahan itulah yang menjadi ciri x86 antara perhitungan alamat dan akses data.

Menggunakan skala di luar 1, 2, 4, dan 8. Bentuk [rdi + rsi*3] ditolak assembler karena perangkat keras hanya menyediakan empat faktor skala. Perkalian tiga kali terhadap satu nilai dinyatakan sebagai [rdi + rdi*2], dengan basis dan indeks menggunakan register yang sama.

Mengira LEA memperbarui flag. Percabangan yang ditempatkan tepat setelah LEA akan membaca flag sisa instruksi sebelumnya, bukan hasil LEA, sehingga keputusannya tidak mencerminkan perhitungan yang baru dijalankan. Flag hanya diperbarui oleh instruksi aritmetika seperti add, sub, dan cmp.

Ringkasan#

  • Alamat efektif x86-64 berbentuk basis ditambah indeks dikalikan skala ditambah displacement, dengan skala 1, 2, 4, atau 8.
  • LEA menghitung alamat efektif ke register tanpa akses memori dan tanpa memperbarui flag.
  • Penghitungan alamat elemen array menggunakan LEA untuk alamatnya dan MOV untuk isinya.
  • Compiler menggunakan LEA sebagai aritmetika murah untuk perkalian dengan faktor kecil, sedangkan MUL menyediakan perkalian penuh dengan efek samping rdx dan flag.

Referensi#