Assembly SSE — SIMD Vector Instructions for Parallel Processing
In this tutorial, you will learn about Assembly SSE. We cover key concepts, practical examples, and best practices to help you master this topic.
SSE (Streaming SIMD Extensions) provides packed single and double precision operations on 128-bit XMM registers for parallel data processing over 2-4 elements at once.
What You'll Learn
- XMM register layout (128-bit)
- Packed arithmetic instructions
- Shuffle and blend operations
- SSE vs scalar performance
Why It Matters
SSE operations Process 4 floats or 2 doubles in one instruction. Doda Browser uses SSE for accelerated CSS animation calculations and image processing filters.
Real-World Use
Image processing (pixel manipulation), audio processing (sample mixing), video encoding (motion estimation), physics simulation (particle systems), and cryptography (AES-NI).
flowchart LR
A["XMM Register"] --> B["4 x 32-bit Float"]
A --> C["2 x 64-bit Double"]
A --> D["16 x 8-bit Byte"]
A --> E["8 x 16-bit Word"]
B --> F["addps, mulps"]
C --> G["addpd, mulpd"]
D --> H["paddb"]
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
XMM Register Layout
XMM0: [ float3 | float2 | float1 | float0 ] 128 bits
bit127 bit63 bit0
XMM0: [ double1 | double0 ] 128 bits
16 XMM registers in 64-bit mode (XMM0-XMM15).
Packed Float Addition
section .data
a dd 1.0, 2.0, 3.0, 4.0
b dd 5.0, 6.0, 7.0, 8.0
result dd 0.0, 0.0, 0.0, 0.0
section .text
movaps xmm0, [a] ; load aligned packed floats
movaps xmm1, [b]
addps xmm0, xmm1 ; packed add
movaps [result], xmm0
; result = [6.0, 8.0, 10.0, 12.0]
Packed Double Addition
section .data
a dq 1.0, 2.0
b dq 3.0, 4.0
result dq 0.0, 0.0
section .text
movapd xmm0, [a] ; load aligned packed doubles
movapd xmm1, [b]
addpd xmm0, xmm1 ; packed double add
movapd [result], xmm0
; result = [4.0, 6.0]
SSE Arithmetic Instructions
; Single precision
addps ; packed add
subps ; packed subtract
mulps ; packed multiply
divps ; packed divide
sqrtps ; packed square root
; Double precision
addpd ; packed add
subpd ; packed subtract
mulpd ; packed multiply
divpd ; packed divide
sqrtpd ; packed square root
; Horizontal operations
haddps ; horizontal add (pairwise)
hsubps ; horizontal subtract
Dot Product Example
section .data
vec1 dd 1.0, 2.0, 3.0, 4.0
vec2 dd 5.0, 6.0, 7.0, 8.0
section .text
movaps xmm0, [vec1]
movaps xmm1, [vec2]
mulps xmm0, xmm1 ; [5, 12, 21, 32]
haddps xmm0, xmm0 ; [17, 53, 17, 53]
haddps xmm0, xmm0 ; [70, 70, 70, 70]
; xmm0[0] = 70 = 1*5 + 2*6 + 3*7 + 4*8
Data Movement
movaps ; move aligned packed single (16-byte aligned)
movups ; move unaligned packed single
movapd ; move aligned packed double
movupd ; move unaligned packed double
movhlps ; move high to low
movlhps ; move low to high
movshdup ; shuffle odd float elements
movsldup ; shuffle even float elements
Shuffle and Blend
section .data
a dd 1.0, 2.0, 3.0, 4.0
b dd 5.0, 6.0, 7.0, 8.0
section .text
movaps xmm0, [a]
movaps xmm1, [b]
; shuffle: select from a,b based on immediate byte
; shufps dest, src, imm8
; imm8 encodes 4 selections: dest[0..3] from {dest, src}[0..3]
shufps xmm0, xmm1, 0xE4
; xmm0 = [1.0, 2.0, 7.0, 8.0]
; blend: select element-by-element
; blendps dest, src, imm8
; imm8 bit n = 1 selects from src, 0 from dest
blendps xmm0, xmm1, 0b1010
Integer SSE Instructions
section .data
byte_a db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
byte_b db 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
section .text
movdqu xmm0, [byte_a] ; load 16 bytes
movdqu xmm1, [byte_b]
paddb xmm0, xmm1 ; packed byte add (16 at once)
; Other integer ops
paddw ; packed word add
paddd ; packed dword add
paddq ; packed qword add
psubb ; packed byte subtract
pmullw ; packed word multiply
Comparison and Min/Max
section .data
a dd 1.0, 5.0, 3.0, 8.0
b dd 4.0, 2.0, 6.0, 7.0
section .text
movaps xmm0, [a]
movaps xmm1, [b]
cmpps xmm0, xmm1, 0 ; 0=CMP_EQ, 1=CMP_LT, 2=CMP_LE
; result mask: all 1s or all 0s per element
; Min/max
minps xmm0, xmm1 ; element-wise minimum
maxps xmm0, xmm1 ; element-wise maximum
Common Mistakes
1. Alignment faults with movaps
movaps requires 16-byte alignment. Use movups for unaligned data or align your data sections.
2. Mixing packed and scalar instructions
addps uses all 4 elements. addss uses only element 0. Mixing them gives wrong results.
3. Forgetting the shuffle immediate encoding
The 8-bit immediate in shufps encodes element selection positionally. Memorize the pattern or use NASM macros.
4. Not clearing XMM registers before use
SSE divides by zero or compares with stale data produce undefined results. Use xorps xmm0, xmm0 to zero.
5. Ignoring SSE version requirements
SSE (original), SSE2 (double support), SSE3 (horizontal ops), SSSE3, SSE4.1, SSE4.2. Check CPU support via CPUID.
Practice Questions
1. How many floats can one SSE instruction process?
4 single-precision floats in one 128-bit XMM register.
2. What is the difference between movaps and movups?
movaps requires 16-byte aligned memory (faster). movups works with any alignment (slightly slower).
3. How do you compute a dot product using SSE?
Multiply element-wise with mulps, then use haddps twice to sum all elements.
4. What does shufps do?
Shuffles elements between two XMM registers based on an 8-bit immediate control byte.
Challenge: Write an SSE function that computes the sum of squared differences between two float arrays.
Solution
; float ssd(float* a, float* b, int n)
; a in RDI, b in RSI, n in RDX
ssd:
xorps xmm0, xmm0 ; accumulator
xor rcx, rcx
.loop:
movups xmm1, [rdi + rcx * 4]
movups xmm2, [rsi + rcx * 4]
subps xmm1, xmm2 ; diff
mulps xmm1, xmm1 ; square
addps xmm0, xmm1 ; accumulate
inc rcx
cmp rcx, rdx
jl .loop
; Horizontal sum
haddps xmm0, xmm0
haddps xmm0, xmm0
ret
FAQ
{{< faq question="Is SSE available on all x86-64 CPUs?" >}} Yes, SSE2 is mandatory for x86-64. SSE4.2 is available on most modern CPUs (Intel Nehalem+, AMD Bulldozer+). {{< /faq >}}
{{< faq question="What is the difference between SSE and AVX?" >}} SSE uses 128-bit registers. AVX extends to 256-bit and uses 3-operand syntax (dest, src1, src2). {{< /faq >}}
{{< faq question="How do I check if a CPU supports SSE4.2?" >}} Use the CPUID instruction with EAX=1, check bit 20 of ECX for SSE4.2 support. {{< /faq >}}
{{< faq question="Can SSE instructions access memory directly?" >}}
Yes, many SSE instructions have memory operand variants: addps xmm0, [mem] loads from memory and adds.
{{< /faq >}}
{{< faq question="What is the penalty for misaligned SSE access?" >}} On older CPUs, movups with misaligned data is slower (crossing cache line boundary). On modern CPUs, the penalty is minimal. {{< /faq >}}
Mini Project
Write an SSE-based array magnitude computation.
section .data
array dd 3.0, 4.0, 5.0, 12.0
n dd 4
section .bss
magnitudes resd 4
section .text
global _start
_start:
movaps xmm0, [array]
mulps xmm0, xmm0 ; squares
sqrtps xmm0, xmm0 ; square roots
movaps [magnitudes], xmm0
; magnitudes = [3.0, 4.0, 5.0, 12.0]
mov rax, 60
xor rdi, rdi
syscall
What's Next
Now that you understand SSE, proceed to AVX (Advanced Vector Extensions).
| Topic | Description | Link |
|---|---|---|
| AVX | 256-bit SIMD vectors | {{< ref "17-avx" >}} |
| Floating-point | Scalar FP operations | {{< ref "15-floating-point" >}} |
| Instructions | CPU instruction set | {{< ref "06-instructions" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro