Aritmetika load-store

Pola aritmetika hanya antar register dan akses memori hanya melalui load dan store.

Daftar isi

RISC-V mengikuti pola load-store, yaitu aturan yang memisahkan perhitungan dari akses memori secara ketat. Topik ini menjelaskan keduanya, menunjukkan bentuk instruksi load dan store yang tersedia, lalu membuktikannya dengan program penjumlahan array yang dijalankan di qemu. Perbedaan dengan x86-64 yang membolehkan operan memori pada aritmetika menjadi benang merah seluruh penjelasan.

Aritmetika hanya antar register#

Semua instruksi aritmetika RISC-V menerima register atau immediate sebagai operannya, dan tidak satu pun menerima alamat memori. Instruksi add rax, [rbx] yang sah pada x86-64 tidak memiliki padanan apa pun di sini, sebab penjumlahan yang melibatkan memori harus diuraikan menjadi load terlebih dahulu, kemudian dijumlahkan, dan hasilnya disimpan di register. Aturan ini berlaku untuk seluruh keluarga aritmetika dan perbineran, yaitu add, sub, and, or, xor, serta bentuk gesernya.

Bentuk immediate dari setiap instruksi menerima konstanta 12 bit bertanda, sehingga rentangnya dari -2048 sampai 2047. Konstanta di luar rentang itu tidak dapat ditanam dalam satu instruksi aritmetika dan harus dimuat lewat lui atau pseudo-instruction li. Perkalian dan pembagian integer bahkan tidak ada pada basis RV64I, karena keduanya disediakan ekstensi M melalui instruksi seperti mul dan div, dan profil RV64GC yang digunakan toolchain modul ini sudah mencakupnya.

Penolakan assembler membuktikan aturan ini dengan jelas. Berkas uji berikut berisi satu instruksi add dengan operan memori, dan assembler menolaknya pada saat perakitan.

bash
# buat berkas uji berisi instruksi add dengan operan memori
printf 'add t0, 0(t1), t2\n' > uji.s

# assembler menolaknya karena add tidak menerima akses memori
riscv64-linux-gnu-as -o /dev/null uji.s; echo "exit=$?"

# bersihkan berkas uji
rm uji.s
text Keluaran
uji.s: Assembler messages:
uji.s:1: Error: illegal operands `add t0,0(t1),t2'
exit=1

Pesan error illegal operands menyatakan bahwa kombinasi operan itu tidak dikenal untuk add, tanpa menawarkan bentuk lain. Pada x86-64, kombinasi serupa dirakit tanpa masalah karena arsitekturnya memang menyediakannya. Pemisahan ini membuat setiap instruksi aritmetika dapat dieksekusi tanpa menyentuh memori sama sekali, sehingga jalur datanya lebih pendek dan penjadwalan pipeline, yaitu alur eksekusi berlapis di dalam prosesor, lebih mudah diprediksi.

Memori hanya melalui load dan store#

Akses memori pada RISC-V hanya terjadi pada instruksi load dan store. Basis RV64I menyediakan ld dan sd untuk 64 bit serta lw dan sw untuk 32 bit, ditambah bentuk 16 bit dan 8 bit. Bentuk alamatnya hanya satu, yaitu offset 12 bit bertanda yang dijumlahkan dengan sebuah register basis, sehingga ld t5, 0(t4) membaca dari alamat t4 ditambah nol. Tidak ada mode pengalamatan basis plus indeks dikalikan skala seperti [rbx + rcx*8 + 24] milik x86-64, sehingga alamat seperti itu harus dihitung dengan slli dan add terlebih dahulu.

Setiap instruksi juga paling banyak melakukan satu akses memori. Load membaca dan menuliskan hasilnya ke register tanpa menyentuh memori lagi, dan store menulis tanpa menghitung apa pun. Kontras dengan x86-64 yang membolehkan add mem, reg membaca sekaligus menulis memori dalam satu instruksi, atau instruksi string yang berulang kali menyentuh memori. Aturan satu akses ini yang membuat unit load-store prosesor RISC-V dapat dirancang sederhana, sedangkan kompleksitas pengalamatan pindah ke instruksi perhitungan alamat.

Demo penjumlahan array#

Program berikut menjumlahkan lima elemen array dengan pola load-store penuh. Setiap elemen dimuat satu per satu lewat ld, dijumlahkan ke register penjumlah, dan tidak ada satu pun operasi aritmetika yang menyentuh memori.

asm jumlah-array.asm
.global _start
.text
_start:
    la      t0, data            # alamat dasar array
    li      t1, 5               # banyak elemen
    li      t2, 0               # indeks elemen
    li      a0, 0               # penjumlah, dimulai dari nol
ulang:
    slli    t3, t2, 3           # offset byte = indeks * 8
    add     t4, t0, t3          # alamat elemen terkini
    ld      t5, 0(t4)           # muat elemen ke register
    add     a0, a0, t5          # tambahkan ke penjumlah
    addi    t2, t2, 1           # indeks berikutnya
    blt     t2, t1, ulang       # lanjut selama indeks < 5
    li      a7, 93              # syscall exit
    ecall
.section .rodata
data:
    .dword 12, 7, 30, 3, 18    # lima nilai, totalnya 70
bash
# rakit dan tautkan program
riscv64-linux-gnu-as -o jumlah-array.o jumlah-array.asm
riscv64-linux-gnu-ld --no-relax -o jumlah-array jumlah-array.o

# jalankan di qemu, kode keluarnya jumlah seluruh elemen
qemu-riscv64 ./jumlah-array; echo "exit=$?"
text Keluaran
exit=70

Kode keluar 70 sama dengan 12 + 7 + 30 + 3 + 18, yang berarti kelima elemen termuat dan dijumlahkan dengan benar. Perhitungan alamat menggunakan slli t3, t2, 3 untuk mengalikan indeks dengan 8, karena ld membaca 8 byte per elemen dan offset instruksinya hanya mengisi bagian konstanta dari alamat. Pola ini sama persis dengan yang dilakukan compiler pada mode optimasi rendah, yaitu menghitung alamat ke register, memuat, lalu berhitung.

Array itu sendiri berada pada section .rodata yang diisi lewat direktif .dword, sehingga datanya bersifat baca saja dan setiap unsurnya berukuran 8 byte. Alamat dasarnya diperoleh melalui pseudo-instruction la, yang dirakit menjadi pasangan auipc dan addi sebagaimana terlihat pada pembahasan encoding. Register a0 digunakan secara ganda sebagai penjumlah sekaligus pembawa kode keluar, sehingga program tidak perlu menyalin hasilnya lagi sebelum ecall.

Batasan offset dan konsekuensinya#

Offset 12 bit pada load dan store hanya mencakup -2048 sampai 2047 byte dari register basis. Data yang letaknya lebih jauh harus dijangkau dengan menghitung alamatnya ke sebuah register terlebih dahulu, misalnya melalui auipc hasil perakitan pseudo-instruction la. Batasan ini terasa ketat dibandingkan dengan displacement 32 bit milik x86-64, tetapi dalam praktiknya frame stack, yaitu blok memori stack milik satu fungsi, dan struktur data berukuran kecil berada di dalam rentang itu.

Ukuran muatan load dan store juga bervariasi, dan perlakuan tandanya perlu dicermati. Instruksi lw memuat 32 bit lalu memperluas tandanya menjadi 64 bit, sedangkan lwu memuat 32 bit tanpa perluasan tanda, dan keduanya disediakan berdampingan karena kasus penggunaannya berbeda. Bentuk 8 bit dan 16 bit yang bertanda mengikuti pola lw, sedangkan padanan tanpa tanda seperti lbu dan lhu mengikuti pola lwu.

Batasan serupa berlaku pada immediate aritmetika, sehingga konstanta besar selalu dimuat ke register sebelum digunakan. Konsekuensi gabungan dari kedua batasan ini adalah kode yang lebih panjang, tetapi setiap langkahnya eksplisit dan dapat dibaca satu per satu. Desain seperti ini menukar jumlah instruksi dengan kesederhanaan perangkat keras, dan penukaran itulah yang menjadi ciri arsitektur berbasis RISC.

Ringkasan#

  • Aritmetika RISC-V hanya menerima register dan immediate 12 bit, sehingga operasi yang melibatkan memori wajib diuraikan menjadi load terpisah.
  • Akses memori hanya terjadi pada load dan store dengan bentuk alamat offset 12 bit plus register basis, satu akses per instruksi.
  • Penjumlahan array membuktikan pola ini, dengan slli menghitung offset byte dan ld memuat tiap elemen sebelum dijumlahkan.
  • Offset dan immediate yang terbatas membuat kode lebih panjang, sebagai imbalan dekoder dan unit load-store yang sederhana.

Referensi#