Menulis assembly langsung di dalam kode C dengan __asm__ GNU extension: bentuk dasar dan extended asm, constraint "r"/"m"/"i"/"=r" yang menghubungkan register dengan variabel C, clobber list, volatile, contoh rotasi bit dan barrier, serta kapan inline asm layak dipakai di produksi (dan kapan tidak).

Di episode 9 kalian memanggil fungsi assembly dari C; episode 13 memberi kalian macro untuk menyusun ulang kode. Episode 14 menyatukan keduanya: inline assembly — menulis assembly di dalam fungsi C, sehingga compiler yang mengurus pemilihan register dan alokasi. Ini adalah teknik yang dipakai kernel Linux, glibc, dan library kriptografi untuk menjangkau instruksi yang tidak diekspos bahasa C.
Mengapa penting? Kadang kalian butuh instruksi spesifik (rdtsc, clflush, atomics) yang tidak punya builtin C, atau butuh barrier dan urutan eksekusi yang presisi. Inline asm adalah jawabannya. Tapi ingat: inline asm juga membuat compiler tidak bisa mengoptimasi kode di dalamnya — jadi ini alat presisi, bukan pengganti optimasi compiler.
Sintaks dasar GCC adalah asm (atau __asm__) dengan sintaks AT&T:
__asm__("movq %rax, %rdx");Tanpa argumen, bentuk ini hanya menyisipkan instruksi — register di dalamnya adalah register sungguhan. Berbahaya karena compiler tidak tahu kalian mengubahnya. Bentuk ini jarang dipakai kecuali untuk barrier (__asm__ volatile). Yang penting adalah extended asm.
asm [volatile] (
"template"
: output operands
: input operands
: clobbers
: goto labels // opsional, jarang
);Template memakai sintaks AT&T (movq %1, %0). Nomor %0, %1, ... menunjuk operan secara berurutan. Contoh paling sederhana:
long add_one(long y) {
long x;
__asm__(
"addq $1, %0"
: "=r" (x) // output: %0, hasil di register
: "0" (y) // input: %1… tidak — "0" = pakai %0 yang sama
);
return x;
}Perhatikan konstanta "0": ini memberitahu compiler input y diletakkan di operand yang sama dengan %0. Input y sudah terisi ke dalam register sebelum instruksi dijalankan — compiler yang memilih registernya.
Constraint adalah huruf yang memberi tahu compiler jenis operan yang boleh dipilih. Yang paling sering dipakai:
| Constraint | Arti |
|---|---|
"r" | register umum (input) |
"=r" | register umum (output) |
"m" | alamat memori |
"i" | immediate (konstanta waktu-compile) |
"g" | umum: register, memori, atau immediate |
"+r" | read-write register |
"=&r" | output early-clobber (ditulis sebelum input habis dibaca) |
Contoh konkret — rotasi bit kanan yang memakai ror (tidak punya operator di C):
unsigned long ror64(unsigned long v, unsigned int n) {
__asm__(
"rorq %b1, %0" // %b1 = byte rendah dari n (ror pakai cl)
: "+r" (v)
: "c" (n) // n dimuat ke rcx
: "cc" // flags berubah
);
return v;
}Warning
Urutan operan output pertama = %0. Urutan di daftar output menentukan nomor operan, diikuti input. Kesalahan paling umum adalah menyangka %0 = input pertama. Telitilah setiap contoh di atas: output selalu %0-%k-1, input mulai dari %k.
Clobber list menyatakan register/flags yang dimodifikasi instruksi, sehingga compiler tidak menyimpan nilai penting di sana:
__asm__("cpuid"
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
: "a" (leaf)
: "cc");"cc" — instruction flags diubah."memory" — memori bisa berubah (barrier memori)."rax", "xmm0" — register dimodifikasi.Melupakan clobber adalah sumber bug paling halus dalam inline asm: compiler menganggap register itu masih utuh, lalu memakai nilainya — hasilnya korupsi acak yang mustahil dilacak.
Jika hasil inline asm tidak dipakai, compiler boleh membuangnya (asm bersifat "pure" menurut perspektif compiler). volatile mencegah itu:
__asm__ volatile("mfence" ::: "memory"); // memory barrier
uint64_t tsc;
__asm__ volatile("rdtsc"
: "=a" (tsc_lo), "=d" (tsc_hi)
:: "rcx");Tanpa volatile, rdtsc yang hasilnya tidak dipakai bisa dihapus oleh compiler. Dengan volatile, GCC menjamin instruksi tetap ada.
Mari gabungkan semuanya — membaca vendor ID CPU dengan cpuid:
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint32_t eax, ebx, ecx, edx;
__asm__ volatile(
"cpuid"
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
: "a" (0) // leaf 0: vendor string
: "cc"
);
char vendor[13];
((uint32_t*)vendor)[0] = ebx; // "Genu"
((uint32_t*)vendor)[1] = edx; // "ineI"
((uint32_t*)vendor)[2] = ecx; // "ntel"
vendor[12] = '\0';
printf("Vendor: %s\n", vendor);
return 0;
}gcc -O2 -o cpuid cpuid_vendor.c
./cpuidOutput: Vendor: GenuineIntel (atau AuthenticAMD, dst). Perhatikan bagaimana "=a"(eax) dst. memberi tahu compiler output register masing-masing — kalian tidak pernah menyentuh register eax secara eksplisit.
Important
Aturan emas kernel Linux: gunakan inline asm hanya bila tidak ada alternatif. Sebelum menulis inline asm, cek dulu: apakah ada builtin compiler? (__builtin_ctz, __atomic_*, __rdtsc dari <x86intrin.h>, intrinsics SIMD <immintrin.h>). Builtin memungkinkan compiler mengoptimasi lintas batas asm; inline asm menghalangi itu.
Layak dipakai:
cpuid, rdtsc, clflush, pause.Jangan dipakai:
-O3 -march=native sering lebih baik).%0 vs %1).volatile → instruksi dibuang compiler."r" (read-only) → undefined behavior; pakai "+r".mov %0, %1; GCC inline asm memakai AT&T.Pada episode 14 ini, kalian telah menjembatani C dan assembly dalam satu fungsi.
Inti yang harus dibawa pulang:
asm( "..." : out : in : clobber )."r", "m", "i", "=r", "+r", "=&r" — bahasa penghubung C↔register."cc", "memory", dan register yang diubah.volatile menjaga instruksi dari optimasi pembuangan.Di episode 15 selanjutnya, kalian menyeberang arsitektur: ARM64 Assembly (aarch64) — register x0–x30, kondisi b.eq/cbz, load-store ldr/str, add/sub dengan shifted operands, dan calling convention AAPCS64 — lengkap dengan perbandingan langkah demi langkah melawan x86-64 yang sudah kalian kuasai. Saatnya membuktikan bahwa konsep assembly bersifat universal.