Komputasi angka pecahan dan vektor di assembly: dari x87 (FPU stack) ke SSE2 (register xmm, addsd/mulsd), packing data dalam packed operations, lalu AVX/AVX-512 dengan register ymm/zmm dan VEX/EVEX encoding, termasuk contoh vektorisasi yang memproses banyak angka dalam satu instruksi.

Semua operasi sejauh ini bekerja pada integer. Tapi dunia nyata penuh angka pecahan (suhu, harga, koordinat, audio, grafik) — dan komputasi modern memprosesnya secara vektor: banyak angka sekaligus dalam satu instruksi. Episode 12 membawa kalian ke floating point dan SIMD (Single Instruction, Multiple Data), dua hal yang bekerja di balik hampir setiap aplikasi modern.
Mengapa penting? Karena ketika kalian menulis kode performa-kritis atau menganalisis disassembly library matematika, kalian akan melihat instruksi addsd, mulps, vaddpd — bukan add. Memahami register xmm/ymm/zmm dan cara data di-pack adalah kunci untuk membaca dan menulis kode tersebut.
Prosesor Intel 8087 memperkenalkan unit floating point terpisah dengan 8 register stack (st0–st7). Operasinya mengikuti pola stack:
fld dword [a] ; push a ke st0
fld dword [b] ; push b ke st0 (a pindah ke st1)
faddp st1, st0 ; st1 = st1 + st0; pop
fstp dword [res] ; simpan st0 ke memori; popx87 masih didukung CPU modern, tetapi tidak disarankan untuk kode baru: stack-based modelnya sulit untuk compiler optimasi, dan default precision-nya bergantung pada kontrol word. Sejak sekitar tahun 2000, SSE2 menjadi cara utama.
SSE2 (diperkenalkan Pentium 4, 2001) menyediakan 16 register xmm 128-bit yang diakses langsung (flat, tanpa stack). Setiap register bisa menampung:
Instruksi scalar bekerja pada elemen pertama saja (sd = scalar double):
section .data
a dq 3.5
b dq 2.25
res dq 0.0
section .text
movsd xmm0, [a] ; xmm0 = 3.5 (elemen pertama)
movsd xmm1, [b] ; xmm1 = 2.25
addsd xmm0, xmm1 ; xmm0 = 5.75
mulsd xmm0, xmm0 ; xmm0 = 33.0625
movsd [res], xmm0 ; simpan ke memoriInstruksi floating point memakai ABI-nya sendiri: argumen float/double lewat xmm0–xmm7, hasil di xmm0, dan xmm0–xmm15 semuanya caller-saved (aturan System V dari episode 9).
cvtsi2sd xmm0, rax ; convert int64 rax → double di xmm0
cvttsd2si rax, xmm0 ; convert double → int64 (truncate, tanpa round)
cvtsd2si rax, xmm0 ; convert double → int64 (round)Kekuatan sesungguhnya muncul saat data di-pack: satu instruksi memproses beberapa angka sekaligus.
section .data
va dd 1.0, 2.0, 3.0, 4.0
vb dd 10.0, 20.0, 30.0, 40.0
vres dd 0.0, 0.0, 0.0, 0.0
section .text
movaps xmm0, [va] ; muat 4 float (16 byte)
movaps xmm1, [vb]
addps xmm0, xmm1 ; 4 penjumlahan dalam satu instruksi!
movaps [vres], xmm0
; vres = 11.0, 22.0, 33.0, 44.0Kata kunci ps = packed single, pd = packed double. movaps butuh memori aligned 16-byte; movups tidak (lebih fleksibel, sedikit lebih lambat).
AVX (2011) menggandakan lebar register menjadi 256-bit (ymm0–ymm15 — ymm0 berbagi fisik dengan xmm0). AVX juga memperkenalkan encoding VEX dan sintaks 3 operan yang tidak destruktif:
vmovapd ymm0, [a] ; muat 4 double
vmovapd ymm1, [b]
vaddpd ymm2, ymm0, ymm1 ; ymm2 = ymm0 + ymm1 (ymm0/ymm1 utuh!)Bandingkan dengan SSE addpd xmm0, xmm1 yang menimpa xmm0 — VEX memungkinkan hasil ditulis ke register ketiga, memudahkan pipeline dan menghindari dependency. AVX-512 memperluasnya ke zmm 512-bit dengan mask registers, dan EVEX encoding untuk memfasilitasinya.
Note
Kekacauan naming yang wajib diingat: menulis ke ymm0 menghapus seluruh xmm0, dan menulis ke zmm0 menghapus ymm0/xmm0. Menulis ke xmm0 saja (dengan instruksi VEX) justru menolak bagian atas — ini perbedaan krusial antara "upper zeroing" dan "merging". Salah memakai lebar register adalah sumber bug SIMD paling umum.
Mari hitung rata-rata 8 angka float dengan SIMD — 2 instruksi paket vs 8 penjumlahan scalar:
section .data
vals dd 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0
count equ 8
section .text
global _start
_start:
movaps xmm0, [vals] ; muat 4 pertama
movaps xmm1, [vals + 16] ; muat 4 kedua (offset 16 byte)
addps xmm0, xmm1 ; gabung: [5,7,9,11]
haddps xmm0, xmm0 ; horizontal add: [5+7, 9+11, ...]
haddps xmm0, xmm0 ; → total = 36 di elemen pertama
cvttss2si rax, xmm0 ; rax = 36
; rata-rata = 36/8 — cukup sebagai bukti komputasi
mov rax, 60
mov rdi, 36 ; exit code bukti
syscallTip
Ini persis yang dilakukan compiler saat kalian menulis loop sederhana di C dengan -O3 -mavx2. Buka Godbolt (episode 20) dan bandingkan output assembly C kalian — kalian akan melihat pola addps/vaddps yang sama. SIMD bukan sihir; itu hanya data ber-pack yang diproses serentak.
movaps ke alamat yang tidak kelipatan 16 → SIGSEGV. Gunakan movups jika ragu, atau jamin alignment dengan deklarasi yang tepat.xmm ↔ ymm) — lihat Callout naming di atas.xmm0, bukan st0.xmm setelah memakai ymm (upper-merging) menyebabkan stall di beberapa CPU; bersihkan dengan vzeroupper sebelum pindah mode.Pada episode 12 ini, kalian telah memasuki dunia floating point dan SIMD.
Inti yang harus dibawa pulang:
xmm).addsd/mulsd) vs packed (addps/mulpd) — packed memproses banyak data sekaligus.ymm, VEX, 3-operan) dan AVX-512 (zmm, mask) memperluas lebar & fleksibilitas.cvtsi2sd/cvttsd2si; alignment movaps vs movups.Di episode 13 selanjutnya, kita kembali ke NASM sebagai bahasa: macro, struct & conditional assembly — %define/%assign, %macro/%endmacro untuk membuat instruksi "buatan sendiri", %if/%ifdef untuk kode bersyarat, struc/istruc untuk layout data, dan %include untuk modularisasi file assembly. Saatnya kode assembly kalian jadi terstruktur dan bisa dipakai ulang seperti bahasa tingkat tinggi.