Belajar Assembly - Inline Assembly dalam C (GCC)
Episode 14 of 23

Belajar Assembly - Inline Assembly dalam C (GCC)

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

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

Pendahuluan

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.

Bentuk Dasar

Sintaks dasar GCC adalah asm (atau __asm__) dengan sintaks AT&T:

CBasic asm
__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.

Extended Asm

CExtended 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:

Casm: x = y + 1
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: Bahasa Menghubungkan C dan Register

Constraint adalah huruf yang memberi tahu compiler jenis operan yang boleh dipilih. Yang paling sering dipakai:

ConstraintArti
"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):

Cror via inline asm
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.

Clobbers: Jujur kepada Compiler

Clobber list menyatakan register/flags yang dimodifikasi instruksi, sehingga compiler tidak menyimpan nilai penting di sana:

CClobber register
__asm__("cpuid"
    : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
    : "a" (leaf)
    : "cc");
  • "cc" — instruction flags diubah.
  • "memory" — memori bisa berubah (barrier memori).
  • Nama register, misal "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.

volatile: Jangan Buang Instruksi Saya

Jika hasil inline asm tidak dipakai, compiler boleh membuangnya (asm bersifat "pure" menurut perspektif compiler). volatile mencegah itu:

Cvolatile untuk efek samping
__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.

Contoh Lengkap: Deteksi CPUID

Mari gabungkan semuanya — membaca vendor ID CPU dengan cpuid:

Ccpuid_vendor.c
#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;
}
Compile dan jalankan
gcc -O2 -o cpuid cpuid_vendor.c
./cpuid

Output: 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.

Kapan Memakai (dan Tidak Memakai) Inline Asm

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:

  • Instruksi yang tidak punya builtin: cpuid, rdtsc, clflush, pause.
  • Operasi yang butuh presisi urutan: barriers, spinlock primitive.
  • Akses register khusus yang tak terjangkau C.

Jangan dipakai:

  • Optimasi manual yang bisa dilakukan compiler (-O3 -march=native sering lebih baik).
  • Operasi portabel (asm membuat kode tidak portabel antar arsitektur/compiler).
  • Tanpa pemahaman constraint & clobber — risiko korupsi register.

Common Pitfalls

  • Salah nomor operan (%0 vs %1).
  • Lupa clobber → korupsi register diam-diam.
  • Lupa volatile → instruksi dibuang compiler.
  • Mengubah operand "r" (read-only) → undefined behavior; pakai "+r".
  • Mengasumsikan operand memakai register tertentu — compiler bebas memilih; constraint yang menentukan.
  • AT&T template — jangan tulis Intel mov %0, %1; GCC inline asm memakai AT&T.

Penutup

Pada episode 14 ini, kalian telah menjembatani C dan assembly dalam satu fungsi.

Inti yang harus dibawa pulang:

  • Extended asm: template, output, input, clobber — asm( "..." : out : in : clobber ).
  • Constraint: "r", "m", "i", "=r", "+r", "=&r" — bahasa penghubung C↔register.
  • Clobber wajib jujur: "cc", "memory", dan register yang diubah.
  • volatile menjaga instruksi dari optimasi pembuangan.
  • Utamakan builtin/intrinsics compiler; inline asm hanya untuk yang benar-benar perlu.

Di episode 15 selanjutnya, kalian menyeberang arsitektur: ARM64 Assembly (aarch64) — register x0x30, 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.

Belajar Assembly - Inline Assembly dalam C (GCC) | Belajar Assembly