Percabangan pada RISC-V membandingkan dua register langsung di dalam instruksi cabangnya, tanpa register flag sebagai perantara. Topik ini menjelaskan pasangan compare-and-branch tersebut, rentang offset yang menyertainya, pseudo-instruction cabang yang dirakit assembler, lalu membuktikan semuanya dengan dua program yang dijalankan di qemu, yaitu klasifikasi nilai array dan kontras perbandingan bertanda dengan tak bertanda. Perbandingan langsung membuat kondisi cabang terbaca utuh pada satu instruksi, tanpa perlu menelusuri instruksi cmp yang letaknya dapat berjauhan.
Cabang yang membandingkan langsung#
x86-64 menangani perbandingan dalam dua langkah, yaitu cmp yang mengisi register flag seperti ZF dan SF, lalu jl atau instruksi sejenisnya yang membaca flag itu. RISC-V menggabungkan kedua langkah tersebut menjadi satu. Instruksi cabangnya membandingkan rs1 dengan rs2 di dalam instruksi yang sama, lalu lompat apabila kondisinya terpenuhi, dan tidak ada satu pun register flag di seluruh arsitektur ini. Keputusan perbandingan tidak meninggalkan jejak yang dapat dibaca instruksi berikutnya, sehingga setiap cabang menyatakan kondisinya secara lengkap.
Enam instruksi cabang basis RV64I mencakup seluruh kombinasi kesetaraan dan urutan, baik untuk bilangan bertanda maupun tak bertanda.
| Instruksi | Arti | Cabang diambil apabila |
|---|---|---|
beq rs1, rs2, label | sama dengan | rs1 = rs2 |
bne rs1, rs2, label | tidak sama dengan | rs1 tidak sama dengan rs2 |
blt rs1, rs2, label | lebih kecil, bertanda | rs1 < rs2 sebagai bilangan bertanda |
bge rs1, rs2, label | lebih besar atau sama, bertanda | rs1 >= rs2 sebagai bilangan bertanda |
bltu rs1, rs2, label | lebih kecil, tak bertanda | rs1 < rs2 sebagai pola bit |
bgeu rs1, rs2, label | lebih besar atau sama, tak bertanda | rs1 >= rs2 sebagai pola bit |
Perbedaan versi bertanda dan tak bertanda penting karena perbandingan hanya berbeda pada interpretasi bit tertinggi. Dua register yang memuat pola bit sama persis dapat memenuhi cabang yang berbeda apabila dibaca dengan interpretasi berbeda, dan kontras ini dibuktikan pada bagian akhir topik.
Rentang offset cabang#
Instruksi cabang menyimpan offset lompatan dalam immediate 12 bit, sehingga target hanya dapat berada sekitar 4 KiB di atas atau di bawah posisi cabang. Rentang ini jauh lebih sempit daripada displacement x86-64, tetapi cukup untuk struktur kendali biasa seperti loop dan percabangan bersarang. Apabila target berada di luar rentang, assembler menolaknya dengan pesan error, dan pola yang digunakan compiler adalah mencabang ke arah berlawanan untuk melompati satu instruksi j yang rentangnya lebih jauh.
Immediate cabangnya disimpan sebagai kelipatan 2 byte, karena instruksi RISC-V selalu sejajar pada alamat genap sehingga bit terendah offset tidak perlu disimpan dan tersedia satu bit tambahan untuk rentangnya. Lompatan tak bersyarat j tidak terkena batasan ini karena dirakit dari jal yang immediate-nya 20 bit, sehingga rentangnya sekitar 1 MiB. Struktur kendali sekelas loop dan pemanggilan fungsi berada jauh di dalam rentang tersebut, sedangkan lompatan antar bagian program yang berjauhan tetap memerlukan pola dua langkah itu. Petunjuk praktisnya, apabila error rentang ini muncul, jarak antara cabang dan targetnya biasanya sudah melebihi ratusan instruksi, sehingga pemecahan fungsi atau pemindahan target merupakan perbaikan yang wajar.
Pseudo-instruction cabang#
Enam instruksi cabang di atas hanya menyediakan perbandingan rs1 terhadap rs2, tanpa bentuk dengan urutan operan sebaliknya. Untuk kenyamanan menulis, assembler menerima pseudo-instruction bgt, ble, bgtu, dan bleu, lalu merakitnya menjadi instruksi nyata dengan operand yang tertukar. Keluarga lain membandingkan terhadap nol, yaitu beqz, bnez, blez, bgez, dan bltz, yang semuanya dirakit dengan x0 sebagai salah satu operan perbandingannya. Bentuk-bentuk ini tidak menambah kemampuan apa pun di atas enam instruksi cabang basis, dan itulah wujud nyata peran register nol pada alur kendali. Berkas berikut memuat satu instruksi nyata berdampingan dengan tiga pseudo-instruction agar perakitannya terlihat.
.global _start
.text
_start:
blt a0, a1, lanjut # instruksi nyata, tetap apa adanya
bgt a0, a1, lanjut # pseudo-instruction
ble a2, a3, lanjut # pseudo-instruction
bgtu a4, a5, lanjut # pseudo-instruction
lanjut:
nop# rakit lalu tampilkan byte encoding setiap cabang
riscv64-linux-gnu-as -o pseudo-cabang.o pseudo-cabang.asm
riscv64-linux-gnu-objdump -d pseudo-cabang.o
pseudo-cabang.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <_start>:
0: 00b54863 blt a0,a1,10 <lanjut>
4: 00a5c663 blt a1,a0,10 <lanjut>
8: 00c6d463 bge a3,a2,10 <lanjut>
c: 00e7e263 bltu a5,a4,10 <lanjut>
0000000000000010 <lanjut>:
10: 00000013 nopDaftar tersebut menunjukkan perakitan satu per satu. bgt a0, a1 menjadi blt a1, a0, karena a0 lebih besar daripada a1 ekuivalen dengan a1 yang lebih kecil daripada a0. Pola yang sama mengubah ble a2, a3 menjadi bge a3, a2 dan bgtu a4, a5 menjadi bltu a5, a4. Hanya blt pada baris pertama yang lolos tanpa perubahan karena memang instruksi nyata.
Demo klasifikasi array#
Program berikut menghitung berapa banyak elemen array yang nilainya di bawah 50, lalu mencetak hasilnya sebagai digit. Perulangannya dijaga bge yang keluar apabila indeks mencapai batas, sedangkan klasifikasinya menggunakan blt terhadap ambang 50.
.global _start
.text
_start:
la t0, nilai # alamat array
li t1, 8 # banyak elemen
li t2, 0 # indeks
li a0, 0 # penghitung nilai < 50
ulang:
bge t2, t1, selesai # indeks mencapai 8, keluar loop
slli t3, t2, 3 # offset byte = indeks * 8
add t4, t0, t3 # alamat elemen terkini
ld t5, 0(t4) # muat elemen
li t6, 50
blt t5, t6, kecil # elemen < 50, hitung
j lanjut
kecil:
addi a0, a0, 1
lanjut:
addi t2, t2, 1
j ulang
selesai:
addi t6, a0, 48 # ubah jumlah menjadi digit ASCII
la t0, hasil
sb t6, 0(t0) # simpan digit ke buffer
li a7, 64 # syscall write
li a0, 1
la a1, hasil
li a2, 2 # digit + baris baru
ecall
li a7, 93 # syscall exit
li a0, 0
ecall
.section .data
hasil:
.byte 48, 10 # cadangan '0' dan baris baru
nilai:
.dword 12, 48, 50, 7, 100, 3, 49, 90# rakit dan tautkan program
riscv64-linux-gnu-as -o klasifikasi.o klasifikasi.asm
riscv64-linux-gnu-ld --no-relax -o klasifikasi klasifikasi.o
# jalankan di qemu
qemu-riscv64 ./klasifikasi; echo "exit=$?"5
exit=0Keluaran berupa digit 5 karena hanya 12, 48, 7, 3, dan 49 yang berada di bawah 50, sedangkan 50 itu sendiri tidak dihitung karena blt membandingkan secara ketat. Program ini juga memperlihatkan pola umum percabangan pada loop, yaitu satu cabang penjaga di awal, satu cabang keputusan di tengah, dan j untuk kembali ke awal perulangan.
Buffer keluarannya ditempatkan pada section .data berisi dua byte cadangan, yaitu karakter ‘0’ dan baris baru. Konversi jumlah menjadi digit dilakukan dengan menambahkan 48, yaitu kode ASCII untuk karakter ‘0’, lalu menyimpannya lewat sb tepat pada byte cadangan tersebut. Instruksi j lanjut diperlukan agar elemen yang tidak memenuhi ambang tidak ikut menambah penghitung, sehingga setiap iterasi hanya melewati salah satu dari dua jalur.
Kontras signed dan unsigned#
Perbedaan interpretasi bertanda dan tak bertanda paling mudah dibuktikan dengan pola bit ekstrem. Program berikut membandingkan -1 dengan 1 menggunakan blt untuk versi bertanda dan bgeu untuk versi tak bertanda, lalu mencetak dua digit yang menyatakan cabang mana yang terambil.
.global _start
.text
_start:
li t0, -1 # pola bit 64 bit semua bernilai satu
li t1, 1
li s0, 48 # '0' berarti cabang tidak diambil
li s1, 48
blt t0, t1, s_ya # signed: -1 < 1
j u
s_ya:
li s0, 49 # '1' berarti cabang diambil
u:
bgeu t0, t1, u_ya # unsigned: 0xffffffffffffffff >= 1
j cetak
u_ya:
li s1, 49
cetak:
la t2, hasil
sb s0, 0(t2) # digit hasil perbandingan signed
sb s1, 1(t2) # digit hasil perbandingan unsigned
li a7, 64 # syscall write
li a0, 1
la a1, hasil
li a2, 3 # dua digit + baris baru
ecall
li a7, 93 # syscall exit
li a0, 0
ecall
.section .data
hasil:
.byte 48, 48, 10 # dua cadangan '0' dan baris baru# rakit dan tautkan program
riscv64-linux-gnu-as -o kontras-bltu.o kontras-bltu.asm
riscv64-linux-gnu-ld --no-relax -o kontras-bltu kontras-bltu.o
# jalankan di qemu
qemu-riscv64 ./kontras-bltu; echo "exit=$?"11
exit=0Keluaran 11 menyatakan kedua cabang terambil, dan keduanya memang harus terambil pada saat yang sama. Sebagai bilangan bertanda, -1 lebih kecil daripada 1 sehingga blt melompat. Sebagai pola bit, 0xffffffffffffffff lebih besar daripada 1 sehingga bgeu juga melompat. Dua keputusan yang saling bertentangan berasal dari pola bit yang sama persis, dan itulah alasan pemilihan blt daripada bltu harus disadari setiap kali menulis cabang.
Kaidah praktisnya mengikuti jenis datanya, yaitu perbandingan alamat memori menggunakan versi tak bertanda karena alamat tidak memiliki tanda, sedangkan perbandingan nilai aritmetika menggunakan versi bertanda. Dua digit keluaran dibangun dari register s0 dan s1 yang diisi kode ASCII, kemudian disimpan lewat sb sebelum write dipanggil. Pembacaan digit pertama dan digit kedua sama-sama bernilai 1 membuktikan bahwa kedua keputusan lompatan diambil pada pola bit yang identik.
Ringkasan#
- Cabang RISC-V membandingkan dua register langsung di dalam instruksinya, tanpa register flag dan tanpa instruksi cmp pendahulu.
- Enam instruksi basis mencakup kesetaraan serta urutan dalam versi bertanda dan tak bertanda, dan pilihan interpretasinya menentukan keputusan lompatan.
bgt,ble,bgtu, danbleuadalah pseudo-instruction yang dirakit menjadiblt,bge, danbltudengan operand tertukar.- Offset cabang 12 bit membatasi target sekitar 4 KiB, dan target yang lebih jauh dijangkau lewat pola cabang terbalik yang melompati
j.
Referensi#
- RISC-V ISA Manual — RISC-V International — Mendefinisikan enam instruksi cabang basis beserta format encoding B-nya.
- QEMU User Mode Documentation — QEMU — Dokumentasi qemu-riscv64 yang menjalankan kedua demo cabang topik ini.