Topik ini membahas bentuk tiga operan yang diperkenalkan AVX, yaitu perubahan pada instruksi SIMD yang memisahkan register tujuan dari kedua sumbernya. Bentuk dua operan SSE yang menimpa sumbernya dijelaskan lebih dahulu sebagai pembanding, dilanjutkan dengan arti prefiks v dan encoding VEX di baliknya, kemudian addps dan vaddps dijalankan berdampingan pada satu program. Keluaran gdb dan objdump yang asli memperlihatkan hasil perhitungan sekaligus byte encoding yang berbeda.
Batasan bentuk dua operan#
Setiap instruksi aritmetika SSE hanya menerima dua operan, dan operan kiri berperan ganda sebagai sumber sekaligus tujuan. addps xmm0, xmm1 berarti xmm0 diganti dengan xmm0 ditambah xmm1, sehingga nilai lama xmm0 hilang setelah instruksi berjalan. Bentuk ini disebut destruktif karena sumber pertamanya selalu tertimpa hasil.
Konsekuensinya terasa ketika kedua sumber masih dibutuhkan pada langkah berikutnya. Menghitung c = a + b lalu melanjutkan dengan a dan b yang utuh menuntut satu salinan tambahan, misalnya movaps xmm2, xmm0 sebelum penjumlahan, atau memuat ulang salah satu array dari memori. Compiler yang mengalokasikan register untuk ekspresi berganda harus memperhitungkan penimpaan ini di setiap langkah, dan salinan tambahan yang dihasilkannya menambah ukuran kode serta waktu eksekusi. Batasan ini juga membuat pembacaan kode perlu kehati-hatian, karena satu instruksi yang tampak sebagai penjumlahan biasa sekaligus membatalkan salah satu masukannya. Harga satu instruksi ekstra berulang di setiap titik yang membutuhkan nilai asalnya, sehingga tidak dapat dihapus sekali saja untuk seluruh fungsi, dan kebutuhan inilah yang membuat bentuk tiga operan ditunggu sejak lama, bukan sekadar tambahan kosmetik.
Prefiks v dan encoding VEX#
AVX menambahkan huruf v di depan mnemonic yang sudah ada, sehingga addps menjadi vaddps, dan bentuk barunya menerima tiga operan dengan urutan tujuan, sumber pertama, sumber kedua. Bentuk seperti ini disebut non-destruktif (non-destructive) karena tidak ada sumber yang tertimpa hasil. Huruf v pada kode sumber mewakili prefiks VEX pada encoding mesinnya, yaitu prefiks khusus yang menggantikan gabungan byte prefiks lama dan menyediakan satu bidang ekstra untuk register sumber ketiga. Bidang lebar pada prefiks yang sama juga memisahkan bentuk xmm dan ymm, sehingga satu mnemonic melayani kedua lebar tanpa mnemonic baru.
Kebanyakan instruksi ber-VEX juga melonggarkan syarat penyelarasan operan memori, sehingga alat-alat penghasil kode memilih bentuk v secara konsisten. Pada sebagian prosesor, instruksi SSE lama yang dicampur dengan kode AVX menimbulkan penalti transisi, dan menghindarinya menjadi alasan tambahan untuk menggunakan bentuk VEX ketika target mendukung AVX. Instruksi integer 256-bit dari keluarga ini menyusul bersama AVX2, sedangkan contoh pada topik ini sengaja tetap menggunakan register xmm 128-bit agar perbandingannya murni menyentuh bentuk operannya.
Perbandingan pada satu program#
Program berikut memuat dua array float, lalu menjumlahkannya dua kali dengan cara yang berbeda. addps menimpa sumber pertamanya, sedangkan vaddps menuliskan hasilnya ke register ketiga dan membiarkan kedua sumbernya utuh. gdb menghentikan program setelah keempat instruksi selesai dan mencetak ketiga register yang terlibat. Perbandingan ini sekaligus menunjukkan mengapa compiler yang menargetkan AVX memperoleh kode yang lebih padat tanpa mengubah algoritmanya.
default rel
section .data
align 16
a: dd 1.0, 2.0, 3.0, 4.0
b: dd 10.0, 20.0, 30.0, 40.0
section .text
global _start
_start:
movaps xmm0, [a]
movaps xmm1, [b]
addps xmm0, xmm1 ; dua operan: sumber pertama tertimpa hasil
vaddps xmm2, xmm0, xmm1 ; tiga operan: kedua sumber tetap utuh
mov eax, 60
xor edi, edi
syscall# rakit dan tautkan program
nasm -f elf64 avx-tiga-operan.asm -o avx-tiga-operan.o
ld avx-tiga-operan.o -o avx-tiga-operan
# ketiga register setelah empat instruksi penjumlahan dijalankan
gdb -batch -ex 'break _start' -ex 'run' -ex 'stepi 4' -ex 'p $xmm0.v4_float' -ex 'p $xmm1.v4_float' -ex 'p $xmm2.v4_float' ./avx-tiga-operanBreakpoint 1 at 0x401000
Breakpoint 1, 0x0000000000401000 in _start ()
0x0000000000401015 in _start ()
$1 = {11, 22, 33, 44}
$2 = {10, 20, 30, 40}
$3 = {21, 42, 63, 84}Keluaran mengonfirmasi ketiga perilaku yang diperkirakan. Register xmm0 berisi {11, 22, 33, 44} karena addps menimpanya dengan jumlah kedua array, sedangkan xmm1 tetap {10, 20, 30, 40} karena addps tidak pernah menyentuh sumber keduanya. Register xmm2 berisi {21, 42, 63, 84}, yaitu jumlah dari isi xmm0 dan xmm1, dan nilai itu sekaligus membuktikan bahwa kedua sumber vaddps tetap utuh. Mencapai hasil yang sama dengan instruksi SSE saja memerlukan satu movaps tambahan untuk menggandakan salah satu sumber sebelum penjumlahan.
Struktur encoding kedua instruksi dapat dilihat lewat objdump pada berkas objeknya.
# encoding tiap instruksi pada berkas objek
objdump -d -M intel avx-tiga-operan.o | expand
avx-tiga-operan.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0: 0f 28 05 00 00 00 00 movaps xmm0,XMMWORD PTR [rip+0x0] # 7 <_start+0x7>
7: 0f 28 0d 00 00 00 00 movaps xmm1,XMMWORD PTR [rip+0x0] # e <_start+0xe>
e: 0f 58 c1 addps xmm0,xmm1
11: c5 f8 58 d1 vaddps xmm2,xmm0,xmm1
15: b8 3c 00 00 00 mov eax,0x3c
1a: 31 ff xor edi,edi
1c: 0f 05 syscallBaris addps dikodekan dengan 0f 58 c1, yaitu byte escape 0f, opcode 58, dan satu byte modrm yang memuat dua register. Baris vaddps diawali byte c5, penanda prefiks VEX dua byte, sedangkan opcode 58-nya sama persis dengan versi SSE-nya. Dua operan pertama tetap dikodekan pada byte modrm d1, yaitu xmm2 sebagai tujuan dan xmm1 sebagai sumber kedua, sedangkan sumber pertama xmm0 dikodekan pada bidang khusus di dalam prefiks VEX. Nilai f8 pada prefiks itu sekaligus menandai lebar 128 bit, sehingga satu byte prefiks membawa informasi yang pada encoding lama tersebar di beberapa byte. Pada instruksi yang versi SSE-nya memerlukan beberapa prefiks tambahan, bentuk VEX justru menghasilkan kode yang lebih pendek.
Ringkasan#
- Instruksi SSE hanya menerima dua operan dan selalu menimpa sumber pertamanya, sehingga menjaga kedua sumber memerlukan salinan tambahan.
- Bentuk tiga operan AVX menuliskan tujuan, sumber pertama, dan sumber kedua secara terpisah, sehingga tidak ada sumber yang hancur.
- Huruf
vpada mnemonic adalah prefiks VEX pada encoding mesin, yang juga membawa register sumber ketiga dan informasi lebar data. - Kebanyakan instruksi ber-VEX tidak mensyaratkan operan memori selaras, dan compiler memilih bentuk ini karena mengurangi instruksi salinan.
Referensi#
- Intel 64 and IA-32 Architectures Software Developer’s Manual — Intel — bab pengantar Volume 2 mendokumentasikan encoding VEX, bidang operannya, dan latar penalti transisi AVX.
- NASM: The Netwide Assembler manual versi 2.16.03 — NASM
— bab instruksi AVX memuat sintaks tiga operan yang diterima NASM untuk instruksi berprefiks
v.