Tidak ada pernyataan if pada tingkat mesin. Setiap keputusan yang dibuat program x86-64 berujung pada instruksi jump, yaitu instruksi yang memindahkan kendali eksekusi ke label yang ditunjuknya. Topik ini membahas keluarga jump bersyarat yang membaca flag individual, perbedaan tafsir signed dan unsigned atas bit yang sama, jmp yang selalu diambil, serta program klasifikasi bilangan yang menunjukkan cara kerja seluruh mekanisme tersebut.
Flag sebagai dasar keputusan#
Percabangan tidak membandingkan nilai secara langsung. Yang dibaca adalah flag, yaitu bit-bit status yang ditinggalkan instruksi sebelumnya. Instruksi cmp memperbarui flag dengan cara yang sama seperti sub, tetapi hasil pengurangannya tidak disimpan ke register maupun memori, sehingga perbandingan tidak mengubah data apa pun. Setelah cmp dijalankan, ZF menyala bila kedua operan sama, SF menyalin bit tertinggi hasil pengurangan, OF menyala bila hasil keluar dari jangkauan signed, dan CF menyala bila pengurangan unsigned meminjam.
Karena keputusan hanya bergantung pada flag, pola percabangannya selalu sama, yaitu perbandingan lebih dahulu, lalu satu jump bersyarat, lalu dua jalur program yang mungkin dilalui. Fragmen berikut memeriksa kesamaan rax dengan 42 dan menyimpan jawabannya di ebx.
cmp rax, 42 ; hitung rax - 42, buang hasilnya
je sama ; ZF menyala berarti rax sama dengan 42
mov ebx, 1 ; jalur ketika tidak sama
jmp selesai
sama:
mov ebx, 0 ; jalur ketika sama
selesai:Perbandingan dan keputusan adalah dua instruksi terpisah yang dihubungkan oleh flag. Pemisahan inilah yang membuat satu perbandingan dapat melayani beberapa jump berturut-turut dengan kondisi berbeda, sebab flag tetap berlaku sampai ada instruksi lain yang menimpanya.
Keluarga jcc dan tabel kondisinya#
Seluruh jump bersyarat dikenal sebagai keluarga jcc, dengan cc sebagai singkatan condition code. Setiap anggota memeriksa kombinasi flag yang berbeda, sedangkan perilaku yang lain identik, yaitu melompat ke label bila kondisi terpenuhi dan berlanjut ke instruksi berikutnya bila tidak. Tabel berikut merangkum anggota yang paling sering digunakan, dengan a sebagai operan pertama dan b sebagai operan kedua pada cmp.
| Instruksi | Flag yang diperiksa | Arti setelah cmp a, b |
|---|---|---|
je, jne | ZF | a sama atau tidak sama dengan b |
js, jns | SF | hasil negatif atau tidak negatif |
jo, jno | OF | overflow signed atau tidak |
jb, jbe, ja, jae | CF, dan ZF untuk jbe dan jae | perbandingan unsigned |
jl, jle, jg, jge | SF dan OF, dan ZF untuk jle dan jge | perbandingan signed |
Nama anggotanya mengikuti sudut pandang perbandingan. Huruf b dan a berasal dari below dan above untuk tafsir unsigned, sedangkan l dan g berasal dari less dan greater untuk tafsir signed. Huruf e menilai kesamaan melalui ZF saja, dan bentuk negatifnya ditandai huruf n, misalnya jne untuk jump if not equal. Pasangan berhuruf n juga melengkapi setiap kelompok, sehingga ada jns dan jno di samping js dan jo. Dua tafsir itu memungkinkan satu perbandingan menghasilkan dua keputusan berbeda atas bit yang sama, dan kasusnya diperlihatkan oleh program berikut.
jl dan jb pada bit yang sama#
Satu pola bit dapat berarti dua bilangan yang berbeda: pola 0xffffffff berarti -1 bila dibaca signed dan 4294967295 bila dibaca unsigned. cmp menghitung hasil pengurangan yang sama untuk kedua tafsir, sehingga flag yang dihasilkannya juga sama, dan yang berbeda hanyalah pasangan flag yang dibaca. Untuk cmp edi, 1 ketika edi berisi 0xffffffff, hasil 0xfffffffe membuat SF menyala, sedangkan OF dan CF padam. jl memeriksa apakah SF berbeda dengan OF, menemukan keduanya berbeda, dan melompat, sesuai fakta bahwa -1 lebih kecil daripada 1. jb memeriksa CF, menemukan CF padam, dan tidak melompat, sesuai fakta bahwa 4294967295 tidak lebih kecil daripada 1.
Program berikut menjalankan kedua jump tersebut terhadap perbandingan yang sama, lalu melaporkan keputusannya melalui kode keluar (exit code), yaitu angka kecil yang diserahkan program pada saat berhenti. Bit bernilai 1 menandai bahwa cabang signed diambil, dan bit bernilai 4 menandai bahwa cabang unsigned diambil.
; jl-jb.asm
; Perbandingan 0xffffffff dengan 1 dilihat dari dua tafsir.
; jl diambil karena -1 lebih kecil daripada 1 secara signed,
; sedangkan jb tidak diambil karena 4294967295 tidak lebih
; kecil daripada 1 secara unsigned.
; Kode keluar 1 berarti hanya cabang signed yang diambil.
default rel
section .text
global _start
_start:
mov edi, 0xffffffff
cmp edi, 1
uji:
jl terambil_signed ; SF berbeda dengan OF, yaitu -1 < 1
xor ebx, ebx ; cabang signed tidak diambil
jmp lanjut
terambil_signed:
mov ebx, 1 ; cabang signed diambil
lanjut:
mov edi, 0xffffffff
cmp edi, 1
jb terambil_unsigned ; CF menyala berarti lebih kecil secara unsigned
jmp selesai
terambil_unsigned:
or ebx, 4 ; cabang unsigned diambil
selesai:
mov edi, ebx
mov eax, 60
syscall# rakit, tautkan, dan jalankan
nasm -f elf64 jl-jb.asm -o jl-jb.o && ld jl-jb.o -o jl-jb && ./jl-jb; echo "exit=$?"
# amati flag sesudah cmp dan lompatan yang diambil
gdb -batch -ex 'break uji' -ex 'run' -ex 'info registers eflags' -ex 'nexti' ./jl-jbexit=1
Breakpoint 1 at 0x401008
Breakpoint 1, 0x0000000000401008 in uji ()
eflags 0x282 [ SF IF ]
0x000000000040100e in terambil_signed ()Keluaran menunjukkan dua hal. Kode keluar 1 berarti hanya jalur signed yang dilalui. Baris eflags memuat tanda [ SF IF ], yang berarti SF menyala pada saat jl dievaluasi, sedangkan ZF, OF, dan CF tidak tercantum karena padam. Instruksi nexti yang mengikuti mendarat di terambil_signed, sehingga keputusan jump terbaca langsung dari debugger, bukan dari dugaan.
Program klasifikasi bilangan#
Perbedaan antar anggota jcc memungkinkan satu perbandingan melayani beberapa keputusan sekaligus. Program berikut membaca satu nilai dari memori, lalu menggolongkannya sebagai negatif, nol, atau positif. cmp terhadap nol menetapkan SF dan ZF sekaligus, sehingga js menangkap kasus negatif, jz menangkap nol, dan kasus positif jatuh ke jalur tanpa jump apa pun. Hasil penggolongan dikirim sebagai kode keluar, yaitu 2 untuk negatif, 1 untuk positif, dan 0 untuk nol.
; klasifikasi.asm
; Mengklasifikasikan satu nilai sebagai negatif, nol, atau positif.
; Hasilnya dikirim melalui kode keluar, yaitu 2 untuk negatif,
; 1 untuk positif, dan 0 untuk nol.
default rel
section .data
nilai: dq -12
section .text
global _start
_start:
mov rax, [nilai]
cmp rax, 0 ; perbandingan terhadap nol
js negatif ; SF menyala berarti negatif
jz nol ; ZF menyala berarti nol
mov edi, 1 ; selain keduanya berarti positif
jmp selesai
negatif:
mov edi, 2
jmp selesai
nol:
mov edi, 0
selesai:
mov eax, 60 ; syscall exit dengan status edi
syscall# rakit, tautkan, dan jalankan dengan nilai -12
nasm -f elf64 klasifikasi.asm -o klasifikasi.o && ld klasifikasi.o -o klasifikasi && ./klasifikasi; echo "exit=$?"
# ulangi dengan nilai positif
sed 's/dq -12/dq 7/' klasifikasi.asm > klasifikasi-positif.asm
nasm -f elf64 klasifikasi-positif.asm -o klasifikasi-positif.o && ld klasifikasi-positif.o -o klasifikasi-positif && ./klasifikasi-positif; echo "exit=$?"
# ulangi sekali lagi dengan nol
sed 's/dq -12/dq 0/' klasifikasi.asm > klasifikasi-nol.asm
nasm -f elf64 klasifikasi-nol.asm -o klasifikasi-nol.o && ld klasifikasi-nol.o -o klasifikasi-nol && ./klasifikasi-nol; echo "exit=$?"exit=2
exit=1
exit=0Tiga eksekusi di atas menggunakan berkas program yang sama, sedangkan nilainya diubah dengan sed sebelum dirakit ulang. Nilai -12 memenuhi js karena SF menyala, dan programnya berakhir dengan kode keluar 2. Nilai 7 tidak memenuhi js maupun jz, sehingga eksekusinya jatuh ke jalur positif dengan kode keluar 1. Nilai 0 memenuhi jz, dan programnya berhenti dengan kode keluar 0. Susunan seperti ini, yaitu beberapa jump berurutan atas satu perbandingan, adalah bentuk percabangan tiga arah yang paling ringkas di tingkat mesin, dan setiap jalurnya diakhiri jmp selesai supaya eksekusi tidak jatuh ke label berikutnya secara tidak sengaja.
jmp tak bersyarat#
jmp tidak membaca flag dan selalu diambil. Perannya bukan memutuskan, melainkan mengatur jalur eksekusi. Pada program klasifikasi, jmp selesai mencegah jalur positif jatuh ke label negatif. Pada struktur perulangan, jmp mengembalikan kendali ke label kondisi di atas tubuh. jmp juga dapat menunjuk labelnya sendiri, sehingga program berputar di satu instruksi tanpa pernah berakhir.
tunggu:
jmp tunggu ; tidak pernah berakhir tanpa gangguan dari luarPerputaran seperti pada fragmen itu hanya berhenti bila ada gangguan dari luar, misalnya sinyal dari sistem operasi. Di dalam program nyata, bentuk yang sama menjadi kerangka perulangan begitu ada kondisi yang menghentikannya, hanya dengan menambah pemeriksaan flag di dalam tubuhnya. Sifat tanpa syarat jmp juga tampak pada encoding-nya, karena instruksi ini tidak membawa nomor kondisi seperti anggota jcc. Dengan demikian, pemilihan bentuk mengikuti kebutuhan, yaitu cabang bersyarat untuk keputusan dan jmp untuk pengaturan jalur tanpa syarat.
Ringkasan#
- Percabangan assembly membaca flag yang ditinggalkan
cmpatau instruksi lain, bukan membandingkan nilai secara langsung. - Setiap anggota jcc menilai kombinasi flag tertentu, misalnya ZF untuk
jedanjne, SF bersama OF untuk keluarga signedjl,jle,jg, danjge, serta CF untuk keluarga unsignedjb,jbe,ja, danjae. - Bit yang sama dapat menghasilkan keputusan berbeda pada
jldanjbkarena tafsir signed dan unsigned atas pola bit tersebut berbeda. - jmp tidak membaca flag dan selalu diambil, sehingga digunakan untuk mengatur jalur, bukan untuk memutuskan.
Referensi#
- NASM: The Netwide Assembler manual, versi 2.16.03 — NASM — Dokumentasi resmi assembler yang memuat seluruh bentuk instruksi jump beserta catatan encoding-nya.
- Intel 64 and IA-32 Architectures Software Developer Manuals — Intel — Volume 1 memuat tabel kondisi lengkap untuk setiap instruksi jcc beserta definisi flag-nya.