Belajar Assembly - SSE/AVX & Floating Point
Episode 12 of 23

Belajar Assembly - SSE/AVX & Floating Point

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Dua Era Floating Point

x87: FPU Stack (Warisan 1987)

Prosesor Intel 8087 memperkenalkan unit floating point terpisah dengan 8 register stack (st0st7). Operasinya mengikuti pola stack:

x87 (legacy)
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; pop

x87 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: Register XMM dan Model Flat

SSE2 (diperkenalkan Pentium 4, 2001) menyediakan 16 register xmm 128-bit yang diakses langsung (flat, tanpa stack). Setiap register bisa menampung:

  • 2 double precision (64-bit), atau
  • 4 single precision (32-bit), atau
  • 16 byte, 8 word, atau 4 dword integer.

Instruksi scalar bekerja pada elemen pertama saja (sd = scalar double):

SSE2 scalar
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 memori

Instruksi floating point memakai ABI-nya sendiri: argumen float/double lewat xmm0xmm7, hasil di xmm0, dan xmm0xmm15 semuanya caller-saved (aturan System V dari episode 9).

Konversi Integer ↔ Floating

Konversi
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)

SIMD: Packed Operations

Kekuatan sesungguhnya muncul saat data di-pack: satu instruksi memproses beberapa angka sekaligus.

Packed add (4 float 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.0

Kata kunci ps = packed single, pd = packed double. movaps butuh memori aligned 16-byte; movups tidak (lebih fleksibel, sedikit lebih lambat).

AVX, AVX-512, dan Encoding VEX/EVEX

AVX: Register YMM dan 3-Operan

AVX (2011) menggandakan lebar register menjadi 256-bit (ymm0ymm15ymm0 berbagi fisik dengan xmm0). AVX juga memperkenalkan encoding VEX dan sintaks 3 operan yang tidak destruktif:

AVX 3-operan
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.

Vektorisasi: Contoh Rata-rata

Mari hitung rata-rata 8 angka float dengan SIMD — 2 instruksi paket vs 8 penjumlahan scalar:

Rata-rata 8 float (SSE)
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
    syscall

Tip

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.

Common Pitfalls

  • Alignment: movaps ke alamat yang tidak kelipatan 16 → SIGSEGV. Gunakan movups jika ragu, atau jamin alignment dengan deklarasi yang tepat.
  • Mencampur lebar register (xmmymm) — lihat Callout naming di atas.
  • Asumsi endian/urutan elemen — pack diisi elemen pertama di byte terendah.
  • Menggunakan x87 di kode ABI-modern — nilai kembali floating harus lewat xmm0, bukan st0.
  • Performance anomaly — menulis ke xmm setelah memakai ymm (upper-merging) menyebabkan stall di beberapa CPU; bersihkan dengan vzeroupper sebelum pindah mode.

Penutup

Pada episode 12 ini, kalian telah memasuki dunia floating point dan SIMD.

Inti yang harus dibawa pulang:

  • x87 adalah era lama (stack-based); SSE2 adalah cara modern (register flat xmm).
  • Scalar (addsd/mulsd) vs packed (addps/mulpd) — packed memproses banyak data sekaligus.
  • AVX (ymm, VEX, 3-operan) dan AVX-512 (zmm, mask) memperluas lebar & fleksibilitas.
  • Konversi via cvtsi2sd/cvttsd2si; alignment movaps vs movups.
  • Vektorisasi adalah dasar optimasi loop modern — lihat langsung di Godbolt.

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.

Belajar Assembly - SSE/AVX & Floating Point | Belajar Assembly