Memahami optimization levels (-O0 hingga -O3, -Os, -Oz), pass populer (mem2reg, inlining, GVN, loop unroll, LICM, vectorizer), dan praktik membangun pipeline optimasi kustom untuk mengukur dampak tiap pass terhadap IR.

Setelah di episode 7 kita menulis pass pertama dengan New Pass Manager, pada episode ini kita mendalami dunia optimasi LLVM: bagaimana optimizer bekerja, pass mana yang menjalankan apa, dan bagaimana kalian bisa mengontrol pipeline optimasi secara spesifik.
Optimasi adalah alasan utama LLVM menghasilkan kode native yang kompetitif dengan GCC. Memahami pass dan pipeline-nya memberikan kalian kemampuan untuk mendiagnosis mengapa kode tertentu lambat, menulis pass yang lebih cerdas, atau mengoptimalkan aplikasi production.
| Level | Strategi | Trade-off |
|---|---|---|
-O0 | Tanpa optimasi (debug default) | Kode lambat, build cepat, debug mudah |
-O1 | Optimasi ringan yang aman | Keseimbangan awal |
-O2 | Optimasi standar (default release) | Kode cepat, waktu build wajar |
-O3 | Optimasi agresif (loop vectorize, inlining besar) | Lebih cepat, binary lebih besar, build lebih lama |
-Os | Optimasi untuk ukuran | Ukuran binary lebih kecil, sedikit lebih lambat |
-Oz | Agresif ukuran kecil | Ukuran minimal, trade-off performa |
Perbandingan dampak:
clang -emit-llvm -S -O0 -o opt0.ll program.c
clang -emit-llvm -S -O2 -o opt2.ll program.c
clang -emit-llvm -S -O3 -o opt3.ll program.c
wc -l opt0.ll opt2.ll opt3.llBiasanya, -O0 menghasilkan IR yang lebih panjang (karena tidak ada eliminasi dead code), sementara -O3 menghasilkan IR yang lebih pendek tetapi lebih kompleks per instruksi (karena vectorization dan inlining).
Pass fundamental yang mengonversi alloca + load/store menjadi values SSA murni. Ini adalah pass pertama dalam pipeline optimasi.
define i32 @add(i32 %a, i32 %b) {
entry:
%x = alloca i32
store i32 %a, ptr %x
%loaded = load i32, ptr %x
%result = add i32 %loaded, %b
ret i32 %result
}define i32 @add(i32 %a, i32 %b) {
entry:
%result = add i32 %a, %b
ret i32 %result
}Menggabungkan body fungsi yang dipanggil ke dalam fungsi pemanggil, menghilangkan overhead call. Dikontrol oleh heuristik cost/benefit.
Menghapus redundansi komputasi: jika dua expression menghasilkan nilai yang sama, eliminasi yang kedua.
Mengulang body loop beberapa kali untuk mengurangi branch overhead, trade-off ukuran binary.
Memindahkan instruksi yang hasilnya tidak berubah di dalam loop ke luar loop.
Bangun pipeline untuk mengukur dampak tiap pass:
# Baseline: tanpa optimasi
opt -S -O0 -o baseline.ll program.bc
# Hanya mem2reg
opt -S -passes=mem2reg -o after_mem2reg.ll program.bc
# mem2reg + instcombine
opt -S -passes='mem2reg,instcombine' -o after_instcombine.ll program.bc
# Pipeline penuh -O2
opt -S -O2 -o after_o2.ll program.bcGunakan pass -print-after-all untuk melihat IR setelah setiap pass:
opt -S -O2 -print-after-all -disable-output program.bc 2>pipeline.logFile pipeline.log akan berisi IR setelah setiap pass — sangat panjang tetapi sangat informatif.
Tip
Gunakan opt -passes=print<cfg>'*' untuk mencetak CFG (Control Flow Graph) dari setiap function dalam format DOT. Pipeline ini bisa dipipe ke dot untuk menghasilkan grafik visual — sangat berguna untuk memahami bagaimana optimizer mengubah struktur control flow.
Inti yang harus dibawa pulang:
-O0 hingga -O3 dan -Os/-Oz mengontrol kedalaman optimasi.opt -passes= untuk menjalankan pass spesifik satu per satu.-print-after-all menampilkan IR setelah setiap pass — tool debugging pipeline.Di episode 9 selanjutnya kita akan membahas Core API & Data Structures — hierarki Value → User → Instruction, Use edges (def-use chain), IRBuilder, dan program C++ yang membuat module berisi fungsi Fibonacci secara programatik. Sampai jumpa di episode 9!