Jump to content

Intro Compiler & Machine Abstraction

From Haidhi Wiki
Revision as of 07:07, 6 October 2026 by SanzSkuy (talk | contribs) (Created page with "{{Catatan|Catatan ini merangkum dua topik yang saling berkaitan: '''optimisasi compiler''' dan '''abstraksi level mesin''' (ISA, virtual address, processor state x86-64). Materi mengikuti alur pembukaan Bab 3 CS:APP.}} == Compiler Optimization == '''Optimisasi''' adalah proses compiler mengubah kode sumber menjadi bentuk yang lebih cepat, lebih kecil, atau lebih hemat sumber daya, '''tanpa mengubah perilaku program yang teramati dari luar'''. Programmer menulis kode y...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Catatan: Catatan ini merangkum dua topik yang saling berkaitan: optimisasi compiler dan abstraksi level mesin (ISA, virtual address, processor state x86-64). Materi mengikuti alur pembukaan Bab 3 CS:APP.

Compiler Optimization

Optimisasi adalah proses compiler mengubah kode sumber menjadi bentuk yang lebih cepat, lebih kecil, atau lebih hemat sumber daya, tanpa mengubah perilaku program yang teramati dari luar.

Programmer menulis kode yang mudah dibaca manusia; compiler menerjemahkannya menjadi kode mesin yang efisien.

Aturan dasar: as-if rule

Compiler boleh melakukan transformasi apa pun selama program berperilaku seolah-olah tidak diubah. Output akhir harus tetap sama.

⚠️
Warning
Undefined behavior (UB) di C/C++ dapat menghasilkan perilaku yang tampak aneh justru karena aturan ini. Compiler berasumsi UB tidak pernah terjadi, lalu melakukan optimisasi berdasarkan asumsi tersebut. Kode yang "kelihatannya benar" bisa dihapus atau diubah total.

Jenis-jenis optimisasi

Constant folding
Ekspresi konstan dihitung saat kompilasi. x = 2 * 3 + 4 menjadi x = 10.
Dead code elimination
Kode yang tidak pernah dieksekusi, atau yang hasilnya tidak pernah dipakai, dibuang.
Inlining
Pemanggilan fungsi kecil diganti langsung dengan isi fungsinya, menghilangkan overhead call.
Loop unrolling
Badan loop "dibentangkan" untuk mengurangi overhead pengecekan kondisi dan lompatan.
Common subexpression elimination
Ekspresi yang dihitung berulang cukup dihitung satu kali.
Vectorization (SIMD)
Beberapa operasi data diproses sekaligus dalam satu instruksi, memanfaatkan vector register.
Register allocation
Variabel yang sering dipakai ditempatkan di register CPU, bukan di memori.

Tip: Register x86-64 hanya ada 16 buah. Keterbatasan inilah yang membuat register allocation menjadi salah satu masalah tersulit dalam desain compiler. Variabel yang tidak kebagian register harus di-spill ke stack, dan itu jauh lebih lambat.

Flag optimisasi

Flag Keterangan
-O0 Tanpa optimisasi. Paling nyaman untuk debugging karena kode mesin masih memetakan rapi ke kode sumber.
-O1 Optimisasi dasar.
-O2 Standar untuk produksi.
-O3 Lebih agresif, termasuk vectorization dan inlining yang lebih luas.
-Os Mengutamakan ukuran biner yang kecil.

Di Rust, mode optimisasi diaktifkan dengan cargo build --release.

Tip: Saat debugging, gunakan -O0. Pada level optimisasi tinggi, variabel bisa hilang dari debugger, urutan eksekusi berubah, dan breakpoint tidak mendarat di tempat yang diharapkan.

Abstraksi dalam Sistem Komputer

Abstraksi adalah menyembunyikan detail implementasi di balik model yang lebih sederhana. Untuk pemrograman level mesin, ada dua abstraksi yang paling penting.

1. Instruction Set Architecture (ISA)

ISA adalah "kontrak" antara perangkat keras dan perangkat lunak. ISA mendefinisikan:

  • Processor state — apa saja yang disimpan prosesor (register, program counter, condition flag)
  • Format instruksi — bagaimana instruksi dikodekan
  • Efek tiap instruksi terhadap state tersebut

Sebagian besar ISA, termasuk x86-64, mendeskripsikan perilaku program seolah-olah setiap instruksi dieksekusi berurutan: satu instruksi selesai sebelum instruksi berikutnya dimulai.

Kenyataan di perangkat keras

Prosesor nyata jauh lebih rumit dan mengeksekusi banyak instruksi secara konkuren:

  • Pipelining — beberapa tahap instruksi tumpang tindih
  • Superscalar — beberapa instruksi dieksekusi bersamaan
  • Out-of-order execution — urutan eksekusi disusun ulang sesuai kesiapan data
  • Speculative execution — prosesor menebak hasil percabangan sebelum diketahui

Prosesor menggunakan pengaman (dependency checking, reorder buffer, in-order commit) untuk memastikan hasil akhirnya tetap identik dengan model sekuensial yang dijanjikan ISA.

Catatan: Pola yang sama muncul di dua lapisan berbeda. Compiler boleh mengubah urutan kode (as-if rule), hardware boleh mengubah urutan eksekusi — keduanya sah selama perilaku yang teramati tidak berubah.

2. Virtual Address

Alamat memori yang dipakai program level mesin adalah alamat virtual, yang menyajikan memori sebagai satu array byte yang sangat besar dan berurutan.

Implementasi sebenarnya jauh lebih kompleks:

  • Hierarki perangkat keras: register → cache L1/L2/L3 → DRAM → disk
  • Perangkat lunak sistem operasi: page table, MMU, TLB, penanganan page fault, swapping

Sistem operasi mengelola ruang alamat virtual dan menerjemahkan alamat virtual menjadi alamat fisik.

Ukuran ruang alamat x86-64

  • Alamat direpresentasikan sebagai word 64-bit
  • Pada implementasi saat ini, 16 bit teratas harus bernilai nol
  • Ruang alamat efektif = 248 byte ≈ 64 terabyte
  • Program biasa hanya memakai beberapa megabyte sampai beberapa gigabyte
⚠️
Warning
Pada satu waktu, hanya sebagian kecil rentang alamat virtual yang dianggap valid. Akses ke alamat yang tidak valid inilah yang memunculkan segmentation fault.

Abstraksi yang bocor (leaky abstraction)

Kedua abstraksi di atas membuat assembly bisa dipelajari sama sekali. Namun abstraksi tersebut bocor dalam beberapa situasi:

  • Performa — ISA menjanjikan urutan, tetapi cache miss dan branch misprediction tetap memengaruhi kecepatan nyata
  • Konkurensi — model sekuensial tidak lagi cukup ketika beberapa thread berinteraksi, sehingga diperlukan memory barrier dan operasi atomik
  • Keamanan — Spectre dan Meltdown adalah kasus di mana pengaman ternyata tidak sempurna: speculative execution meninggalkan jejak di cache yang bisa dibaca penyerang

Representasi Level Mesin

Compiler mengerjakan bagian terberat: menerjemahkan model eksekusi C yang relatif abstrak menjadi instruksi prosesor yang sangat elementer.

Assembly dan machine code pada dasarnya sama; perbedaannya hanya representasi. Assembly berbentuk teks yang bisa dibaca manusia, machine code berbentuk biner. Hubungan keduanya nyaris satu-ke-satu.

Processor state yang terlihat

Bagian-bagian state prosesor berikut tersembunyi dari programmer C, tetapi terlihat jelas di level mesin.

Program counter (%rip)

Menyimpan alamat memori instruksi berikutnya yang akan dieksekusi. Di C, posisi eksekusi adalah konsep implisit; di level mesin, ini register konkret yang dapat diubah lewat instruksi lompat dan pemanggilan fungsi.

Integer register file

16 lokasi bernama, masing-masing menyimpan nilai 64-bit. Isinya dapat berupa alamat (setara pointer di C) atau data integer.

Register Peran umum (System V AMD64 ABI)
%rax Nilai kembalian fungsi
%rbx Callee-saved
%rcx Argumen ke-4
%rdx Argumen ke-3
%rsi Argumen ke-2
%rdi Argumen ke-1
%rbp Callee-saved / frame pointer
%rsp Stack pointer
%r8 Argumen ke-5
%r9 Argumen ke-6
%r10, %r11 Caller-saved, data sementara
%r12–%r15 Callee-saved

Sebagian register melacak bagian penting dari state program, sebagian lain menampung data sementara seperti argumen, variabel lokal, dan nilai kembalian.

Condition code register

Menyimpan informasi status hasil operasi aritmetika atau logika yang terakhir dieksekusi (zero flag, sign flag, carry, overflow).

Register inilah yang dipakai untuk mengimplementasikan perubahan alur kontrol bersyarat seperti if dan while:

  1. Instruksi pembanding dieksekusi → flag berubah
  2. Instruksi lompat bersyarat membaca flag → alur program bercabang

Catatan: Satu pernyataan if di C selalu "dicicil" menjadi minimal dua langkah terpisah di level mesin: penetapan flag, lalu pembacaan flag.

Vector register

Setiap register dapat menampung satu atau lebih nilai integer atau floating-point sekaligus. Inilah dasar perangkat keras untuk SIMD dan vectorization.

Memori: tipe data adalah ilusi

C menyediakan model di mana objek dengan tipe berbeda dideklarasikan dan dialokasikan di memori. Kode mesin memandang memori semata-mata sebagai array byte besar yang dapat dialamati.

Konsekuensinya:

  • Tipe agregat seperti array dan struct direpresentasikan sebagai kumpulan byte yang bersebelahan
  • Assembly tidak membedakan integer signed dan unsigned
  • Assembly tidak membedakan jenis pointer satu sama lain
  • Assembly bahkan tidak membedakan pointer dari integer
⚠️
Warning
Yang membedakan signed dan unsigned bukan datanya, melainkan instruksi mana yang dipilih untuk mengolahnya. Pola bit-nya identik. Inilah sebabnya kesalahan tipe data di C bisa lolos tanpa gejala sampai jauh di kemudian hari.

Tip: Hal ini juga menjelaskan mengapa casting pointer di C praktis "gratis" di level mesin — tidak ada data yang berubah, hanya cara compiler memperlakukannya.

Isi program memory

Ruang memori program berisi:

  • Kode mesin yang dapat dieksekusi
  • Informasi yang dibutuhkan sistem operasi
  • Run-time stack untuk mengelola pemanggilan dan pengembalian prosedur
  • Blok memori yang dialokasikan pengguna, misalnya melalui malloc

Seluruhnya dialamati menggunakan alamat virtual.

Satu instruksi = satu operasi elementer

Satu instruksi mesin hanya melakukan operasi yang sangat sederhana, misalnya:

  • Menjumlahkan dua angka yang tersimpan di register
  • Memindahkan data antara memori dan register
  • Melompat ke alamat instruksi lain secara bersyarat

Compiler harus menyusun rangkaian instruksi semacam itu untuk mengimplementasikan konstruksi program seperti evaluasi ekspresi aritmetika, loop, serta pemanggilan dan pengembalian prosedur.

Catatan: Inilah gambaran besarnya. C menyediakan objek bertipe, variabel bernama, dan struktur kontrol. Mesin hanya punya 16 register, array byte, flag, dan instruksi sederhana. Jarak antara keduanya diisi oleh compiler — dan membaca hasil pengisian jarak itulah inti dari Bab 3 CS:APP.