Register dan format RV64

x0 hingga x31 beserta enam format encoding R, I, S, B, U, dan J.

Daftar isi

Topik ini memetakan tiga puluh dua register RV64 beserta nama ABI-nya, membuktikan peran khusus x0 melalui program nyata, lalu membedah enam format encoding instruksi RISC-V. Setelah membacanya, keluaran objdump pada topik-topik lain dapat dibaca sampai ke susunan bitnya, dan asal-usul pseudo-instruction yang tampak seperti instruksi tunggal menjadi jelas. Nama ABI seperti a0, t0, dan ra lebih sering muncul pada keluaran assembler daripada nama x0 hingga x31, sehingga keduanya perlu dikuasai sekaligus.

Tiga puluh dua register dan nama ABI-nya#

RV64 menyediakan tiga puluh dua register serbaguna 64-bit yang bernomor x0 sampai x31. Berbeda dengan x86-64 yang mewarisi nama bersejarah seperti rax dan rsp, RISC-V menggunakan penamaan fungsional yang ditetapkan konvensi pemanggilan, yaitu Application Binary Interface (ABI). Kode sumber assembly RISC-V hampir selalu menggunakan nama ABI karena nama itulah yang menyatakan peran register dalam pemanggilan fungsi, bukan nomor perangkat kerasnya.

Tabel berikut merangkum seluruh register dalam kelompok peran yang sama. Baris yang berisi rentang nomor berarti seluruh register dalam rentang itu memiliki peran dan disiplin yang sama.

RegisterNama ABIPeran
x0zerokonstanta nol yang dibaca tetap, tulisannya diabaikan
x1raalamat kembali (return address)
x2spstack pointer
x3gpglobal pointer untuk data statis
x4tpthread pointer
x5 sampai x7t0 sampai t2register sementara
x8s0 atau fpregister terselamatkan, sering menjadi frame pointer
x9s1register terselamatkan
x10 sampai x17a0 sampai a7argumen dan nilai balikan
x18 sampai x27s2 sampai s11register terselamatkan
x28 sampai x31t3 sampai t6register sementara

Kelompok t (temporary) dan a (argument) bersifat caller-saved, yakni nilainya boleh dirusak oleh fungsi yang dipanggil sehingga pemanggil wajib menyelamatkannya sendiri. Kelompok s (saved) bersifat callee-saved, yakni fungsi yang dipanggil wajib memulihkan nilainya sebelum kembali. Pembagian ini diuraikan beserta buktinya pada topik ABI RISC-V, sedangkan gp dan tp jarang disentuh pada program assembly mandiri karena digunakan sistem perangkat lunak yang lebih besar.

x0 sebagai register nol#

x0 adalah register paling istimewa di antara tiga puluh dua register ini karena isinya dikawal perangkat keras. Pembacaan x0 selalu menghasilkan nol, sedangkan penulisannya diabaikan begitu saja tanpa pesan error. Register nol menghilangkan banyak bentuk instruksi yang sebenarnya redundan, karena setiap instruksi yang butuh konstanta nol cukup menyebut x0 sebagai operannya.

Konsekuensinya, beberapa instruksi yang tampak berbeda sebenarnya adalah instruksi yang sama. Salinan register mv rd, rs dirakit menjadi addi rd, rs, 0, pemuatan konstanta kecil li rd, imm dirakit menjadi addi rd, x0, imm, dan nop dirakit menjadi addi x0, x0, 0. Program berikut memanfaatkan x0 sekaligus membuktikan bahwa tulisan ke x0 tidak berdampak apa pun.

asm x0-tetap-nol.asm
.global _start
.text
_start:
    addi    s0, x0, 125     # s0 berisi 125
    addi    x0, s0, 9       # penulisan ke x0 diabaikan perangkat keras
    add     a0, x0, s0      # x0 terbaca sebagai nol, sehingga a0 = 125
    li      a7, 93          # syscall exit
    ecall
bash
# rakit dan tautkan program
riscv64-linux-gnu-as -o x0-tetap-nol.o x0-tetap-nol.asm
riscv64-linux-gnu-ld -o x0-tetap-nol x0-tetap-nol.o

# jalankan di qemu, kode keluarnya nilai a0
qemu-riscv64 ./x0-tetap-nol; echo "exit=$?"
text Keluaran
exit=125

Kode keluar 125 membuktikan dua hal sekaligus. Penulisan addi x0, s0, 9 tidak mengubah isi x0, dan pembacaan x0 pada add a0, x0, s0 tetap menghasilkan nol, sehingga a0 berisi 125 dan itulah nilai yang diteruskan ke syscall exit. Nomor syscall pada Linux riscv64 juga berbeda dari x86-64, yaitu exit bernomor 93 alih-alih 60, dan nomor tersebut diisi ke a7 sebelum ecall.

Pseudo-instruction dan perakitannya#

Nama-nama seperti mv, li, dan nop disebut pseudo-instruction, yaitu mnemonic yang disediakan demi kenyamanan, tidak memiliki encoding sendiri, dan dirakit assembler menjadi instruksi nyata. Pembahasan berikut membedah perakitan ini sekaligus memperkenalkan enam format encoding, karena setiap instruksi nyata harus mengikuti salah satunya. Berkas berikut memuat satu instruksi untuk setiap format, lalu beberapa pseudo-instruction.

asm encoding.asm
.global _start
.text
_start:
    add     t0, t1, t2          # format R
    addi    t0, t1, 5           # format I
    sw      t0, 8(sp)           # format S
    beq     t0, t1, selesai     # format B
    lui     t3, 0x12345         # format U
    jal     ra, selesai         # format J
    mv      a1, a0              # pseudo-instruction salin register
    li      a2, 123             # pseudo-instruction muat konstanta 12 bit
    li      t0, 0x123456789     # pseudo-instruction muat konstanta 35 bit
    nop                         # pseudo-instruction tanpa operasi
selesai:
    ret                         # pseudo-instruction kembali
bash
# rakit sebagai RV64I agar pseudo-instruction terlihat bentuk 4 byte-nya
riscv64-linux-gnu-as -march=rv64i -o encoding.o encoding.asm

# tampilkan byte encoding setiap instruksi
riscv64-linux-gnu-objdump -d encoding.o
text Keluaran

encoding.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <_start>:
   0:	007302b3          	add	t0,t1,t2
   4:	00530293          	addi	t0,t1,5
   8:	00512423          	sw	t0,8(sp)
   c:	02628463          	beq	t0,t1,34 <selesai>
  10:	12345e37          	lui	t3,0x12345
  14:	020000ef          	jal	34 <selesai>
  18:	00050593          	mv	a1,a0
  1c:	07b00613          	li	a2,123
  20:	000922b7          	lui	t0,0x92
  24:	a2b2829b          	addiw	t0,t0,-1493 # 91a2b <selesai+0x919f7>
  28:	00d29293          	slli	t0,t0,0xd
  2c:	78928293          	addi	t0,t0,1929
  30:	00000013          	nop

0000000000000034 <selesai>:
  34:	00008067          	ret

Baris-baris pseudo-instruction memperlihatkan perakitannya secara langsung. mv a1, a0 menempati 4 byte bernilai 0x00050593, dan byte itu terbaca sebagai format I dengan rs1 bernomor 10 yaitu a0, rd bernomor 11 yaitu a1, serta immediate bernilai 0, yaitu konstanta yang menyatu di dalam instruksi. Instruksi aslinya adalah addi a1, a0, 0. Pola yang sama berlaku untuk li a2, 123 yang menjadi addi a2, x0, 123, nop yang menjadi addi x0, x0, 0 dengan encoding 0x00000013, dan ret yang menjadi jalr dengan encoding 0x00008067.

Konstanta besar membutuhkan perlakuan berbeda. li t0, 0x123456789 tidak muat dalam satu instruksi apa pun, sehingga assembler merakitnya menjadi empat instruksi, yaitu lui, addiw, slli, dan addi. Nilai 0x123456789 bahkan tidak muat dalam pola lui ditambah addi karena batasan tanda pada immediate, sehingga assembler memilih urutan lain yang hasilnya sama. Pseudo-instruction menyerahkan seluruh perhitungan ini kepada assembler, dan itulah alasan li digunakan hampir di mana-mana pada kode sumber.

Enam baris pertama berisi enam format berbeda, dan objdump menampilkannya apa adanya karena semuanya instruksi nyata. Opsi -march=rv64i dipilih agar setiap pseudo-instruction dirakit dalam bentuk 4 byte yang terbaca, sedangkan pada profil dengan ekstensi C assembler bebas memilih bentuk 2 byte seperti c.mv dan c.li, sebagaimana ditunjukkan pada topik filosofi RISC-V.

Enam format encoding#

Setiap instruksi RISC-V berukuran 32 bit dan mengikuti salah satu dari enam format. Enam format itu ada karena satu alasan praktis, yaitu jumlah bit yang dibutuhkan setiap jenis instruksi tidak sama, sedangkan ukuran total harus tetap. Diagram berikut menampilkannya berdampingan dengan posisi bit yang konsisten.

312524201915141211760funct7rs2rs1f3rdopcodeimm[11:0]rs1f3rdopcodeimm[11:5]rs2rs1f3imm[4:0]opcodeimm[12|10:5]rs2rs1f3imm[4:1|11]opcodeimm[31:12]rdopcodeimm[20|10:1|11|19:12]rdopcodeFormat RFormat IFormat SFormat BFormat UFormat J
Enam format encoding instruksi 32 bit RISC-V

Bidang-bidangnya memiliki arti yang tetap pada semua format. opcode bersama funct3 dan funct7 mengklasifikasikan instruksi, rd serta rs1 dan rs2 menyimpan nomor register sepanjang 5 bit untuk tiga puluh dua register, dan imm menyimpan immediate tersebut. Format I memuat immediate 12 bit utuh, format S membelahnya menjadi dua bagian agar posisi rs1, rs2, dan opcode tidak bergeser dari format lain, dan format U atau J memuat immediate 20 bit.

Pembacaan encoding dapat diverifikasi pada baris addi t0, t1, 5 yang byte-nya 0x00530293. Susunan bitnya adalah immediate 000000000101, rs1 bernilai 00110 yaitu t1, funct3 bernilai 000, rd bernilai 00101 yaitu t0, dan opcode bernilai 0010011 untuk kelompok aritmetika immediate. Format B dan J menyimpan immediate dengan urutan bit yang tampak teracak, karena bit tanda ditempatkan paling atas dan bit-bit lain diposisikan agar pengawatan perangkat keras tetap sederhana. Immediate cabang pada kedua format itu hanya muat 12 bit ditambah bit tanda, sehingga rentangnya terbatas dan konsekuensinya dibahas pada topik cabang.

Ringkasan#

  • RV64 memiliki tiga puluh dua register 64-bit yang dikenal lewat nama ABI, dengan kelompok t dan a bersifat caller-saved serta kelompok s bersifat callee-saved.
  • x0 dibaca sebagai nol dan tulisannya diabaikan, sehingga mv, li, dan nop hanyalah pseudo-instruction yang dirakit menjadi addi.
  • Konstanta yang tidak muat dalam satu instruksi dirakit menjadi beberapa instruksi oleh assembler, misalnya pola lui dan pengikutnya.
  • Setiap instruksi 32 bit mengikuti format R, I, S, B, U, atau J, dengan bidang opcode dan funct sebagai klasifikasinya serta immediate yang lebarnya berbeda-beda.

Referensi#