Topik ini membahas LLVM sebagai target codegen populer dan kapan compiler baru memilih menargetkan LLVM alih-alih menulis back end sendiri. Pembaca memperoleh penjelasan bentuk LLVM IR sebagai representasi berbasis register virtual dengan tipe statis, contoh IR kanonik untuk fungsi add(a, b) = a + b yang mengikuti LLVM Language Reference, serta perbandingan dua pilihan arsitektural beserta konsekuensinya. Setelah membaca topik ini, pembaca dapat menjelaskan keuntungan menargetkan LLVM, menyebutkan biaya yang dibayar saat memilih jalur tersebut, dan menentukan kapan back end sendiri lebih tepat untuk compiler yang sedang dibangun.
LLVM IR sebagai target populer#
LLVM adalah kerangka compiler yang dipakai sebagai back end oleh sejumlah front end produksi, termasuk Clang untuk C dan C++, Swift, Rust, Zig, dan Julia. Pemisahan yang ditawarkan LLVM mengikuti pola front end, IR, dan back end yang sama dengan GCC, tetapi IR LLVM dirancang sebagai batas yang stabil dan dapat ditargetkan oleh front end pihak ketiga. Front end hanya bertanggung jawab menerjemahkan bahasa sumber menjadi LLVM IR, sedangkan seluruh pipeline optimasi dan back end yang menurunkan IR menjadi kode native untuk berbagai arsitektur menjadi tanggung jawab ekosistem LLVM.
Tiga ciri LLVM IR membuatnya populer sebagai target codegen. Pertama, IR LLVM bertipe statis. Setiap nilai memiliki tipe eksplisit seperti i32, i64, atau ptr, sehingga verifikasi tipe dapat dilakukan pada level IR sebelum codegen native dijalankan. Berbeda dari IR berbasis bytecode stack VM yang tidak menyebut tipe pada setiap instruksi, IR LLVM menyebut tipe pada setiap operand, misalnya add i32 %a, %b menyatakan penjumlahan dua nilai bertipe i32. Kedua, IR LLVM bersifat cross-platform. Front end dapat membangkitkan IR tanpa mengetahui arsitektur sasaran, sedangkan back end LLVM menangani penurunan ke x86-64, ARM, atau RISC-V. Ketiga, LLVM menyediakan infrastruktur pass yang luas, termasuk alokasi register, inlining, propagasi konstanta, dan vektorisasi loop, sehingga front end baru langsung memperoleh optimasi yang setara dengan compiler produksi tanpa menulis pass sendiri.
Bentuk IR: tipe dan register virtual#
Bentuk LLVM IR berada di antara bytecode stack VM dan kode native. Setiap nilai adalah register virtual dengan nama yang ditulis dengan prefiks %, dan jumlah register virtual tidak terbatas. Codegen front end tidak perlu mengalokasikan register fisik sebab pass alokasi register pada back end LLVM mengubah register virtual menjadi register fisik, lengkap dengan spill ke memori bila perlu. Pendekatan ini membebaskan front end dari keputusan register, sebagaimana halnya stack VM, tetapi tetap menghasilkan kode native yang efisien setelah pass back end dijalankan.
Contoh kanonik yang muncul pada dokumentasi LLVM adalah fungsi add(a, b) = a + b. Karena Clang tidak tersedia pada sandbox, IR berikut tidak ditangkap dari mesin, melainkan disusun mengikuti sintaks yang ditetapkan oleh LLVM Language Reference Manual.
; Sumber: int add(int a, int b) { return a + b; }
define i32 @add(i32 %a, i32 %b) {
%result = add i32 %a, %b
ret i32 %result
}Fragmen IR di atas memperlihatkan tiga ciri khas. Pertama, fungsi dideklarasikan dengan define yang menyebut tipe kembalian i32, nama fungsi @add dengan prefiks @ sebagai penanda global, dan daftar parameter bertipe i32 dengan prefiks % sebagai penanda register virtual. Kedua, instruksi add i32 %a, %b adalah instruksi tiga alamat dengan tipe eksplisit, sehingga verifikasi tipe dapat dilakukan pada level IR sebelum codegen native. Ketiga, ret i32 %result mengembalikan nilai bertipe i32 dari register virtual %result. Bentuk yang sama, dengan tambahan direktif attribute dan metadata debug, dihasilkan oleh Clang saat mengompilasi C ke LLVM IR dengan opsi -emit-llvm.
Kapan menargetkan LLVM#
Menargetkan LLVM adalah pilihan yang tepat untuk compiler bahasa baru yang ingin segera memperoleh optimasi produksi. Tiga kondisi membuat pilihan ini menarik. Pertama, bahasa yang ditargetkan adalah bahasa umum yang berjalan pada berbagai arsitektur. Front end cukup membangkitkan IR LLVM, dan back end LLVM menangani penurunan ke x86-64, ARM, RISC-V, WebAssembly, dan arsitektur lain tanpa pekerjaan tambahan. Kedua, kualitas optimasi yang diinginkan setara dengan compiler produksi. LLVM menjalankan puluhan pass optimasi yang sudah teruji, termasuk inlining berbasis profil, alokasi register pewarnaan graf, dan vektorisasi loop, sehingga front end baru langsung memperoleh kinerja yang kompetitif. Ketiga, sumber daya pengembangan terbatas. Menulis back end yang lengkap dengan pass optimasi dan dukungan banyak arsitektur membutuhkan tim yang besar, sedangkan front end yang menargetkan LLVM dapat dibangun oleh tim kecil.
Biaya yang dibayar saat menargetkan LLVM berbentuk dua hal. Pertama, ketergantungan pada ekosistem LLVM. Compiler yang menargetkan LLVM harus mengikuti versi LLVM tertentu, dan pembaruan LLVM dapat mengubah antarmuka IR antar versi. Konsekuensi praktisnya, compiler yang menargetkan LLVM biasanya menyatakan rentang versi LLVM yang didukung dan memerlukan pengujian ulang setiap kali LLVM diperbarui. Kedua, pembatasan semantik yang dipaksakan oleh IR LLVM. IR LLVM memodelkan semantik yang dekat ke mesin, sehingga fitur bahasa yang tidak memiliki padanan langsung, misalnya tail call yang dijamin atau effect system yang ketat, memerlukan pemetaan rumit atau bahkan tidak dapat dinyatakan secara langsung.
Kapan menulis back end sendiri#
Menulis back end sendiri adalah pilihan yang tepat bila sasaran compiler bukan arsitektur umum atau bila kontrol penuh atas codegen menjadi kebutuhan. Tiga kondisi membuat pilihan ini menarik. Pertama, target adalah mesin sasaran khusus yang tidak didukung LLVM. Contoh nyata adalah compiler untuk bahasa yang berjalan pada mesin sasaran eksotis, misalnya mesin stack VM yang dirancang khusus untuk bahasa tertentu, atau mesin yang dikembangkan bersamaan dengan bahasa. Kedua, kontrol penuh atas codegen menjadi kebutuhan, misalnya ketika compiler harus memancarkan kode yang sangat ringkas untuk perangkat tertanam, atau ketika pipeline codegen ingin diintegrasikan dengan profiler khusus yang tidak ada pada LLVM. Ketiga, pembelajaran adalah tujuan utama. Compiler proyek kursus ini menargetkan stack VM sendiri sebab target utamanya adalah pembelajaran konsep codegen, bukan kinerja produksi, sehingga back end yang sederhana lebih mendidik daripada back end LLVM yang besar.
Compiler proyek kursus menggambarkan trade-off ini secara langsung. Codegen pada modul pembangkitan kode menargetkan stack VM dengan enam belas opcode, sehingga pipeline codegen cukup satu pass post-order traversal pada AST untuk membangkitkan bytecode. Bila compiler yang sama menargetkan LLVM, front end harus membangkitkan IR yang bertipe, memodelkan setiap nilai sebagai register virtual, dan memenuhi konvensi pemanggilan yang dipakai LLVM. Hasilnya adalah kode native yang lebih cepat, tetapi kompleksitas codegen naik satu tingkat. Pada jalur pembelajaran, kemudahan membaca bytecode stack VM menang, sedangkan pada compiler produksi, kinerja dan portabilitas LLVM menang.
Kesalahan umum#
Kesalahan konseptual yang paling sering muncul adalah menganggap LLVM sebagai compiler utuh, padahal LLVM adalah kerangka back end. Front end bahasa tetap harus ditulis, dan tugasnya adalah membangkitkan IR LLVM yang valid. Kesalahan kedua adalah menyamakan register virtual LLVM dengan register fisik. Register virtual tidak terbatas jumlahnya dan tidak terkait ke arsitektur, sedangkan pass alokasi register pada back end LLVM mengubahnya menjadi register fisik. Kesalahan ketiga adalah menganggap back end sendiri selalu lebih sederhana daripada menargetkan LLVM. Untuk target umum, menulis back end sendiri yang setara dengan LLVM membutuhkan tim besar, sehingga pilihan tersebut hanya sederhana bila targetnya khusus dan sempit.
Kesalahan keempat adalah menganggap LLVM IR bersifat stabil antar versi. Pada kenyataannya, antarmuka IR LLVM berubah antar versi mayor, sehingga berkas IR yang dibangkitkan oleh satu versi LLVM tidak selalu dapat dibaca oleh versi lain. Front end yang menargetkan LLVM biasanya menyatakan rentang versi yang didukung dan memerlukan pengujian ulang setiap kali LLVM diperbarui. Akibatnya, ketergantungan versi bukan sekadar catatan kaki, melainkan keputusan pengembangan yang memengaruhi siklus hidup compiler.
Ringkasan#
- LLVM IR adalah target codegen populer sebab bertipe statis, cross-platform, dan dikelilingi infrastruktur pass yang luas, sehingga front end baru langsung memperoleh optimasi produksi.
- Bentuk IR LLVM berada di antara bytecode stack VM dan kode native, dengan register virtual tak terbatas yang diturunkan menjadi register fisik oleh pass alokasi register pada back end.
- Menargetkan LLVM tepat untuk bahasa umum yang ingin segera memperoleh optimasi produksi, dengan biaya berupa ketergantungan versi LLVM dan pembatasan semantik IR.
- Back end sendiri tepat untuk target khusus, kontrol codegen penuh, atau tujuan pembelajaran, sebagaimana diterapkan pada compiler proyek kursus yang menargetkan stack VM.
Referensi#
- LLVM Language Reference Manual — LLVM Project
— Sumber kanonik sintaks dan semantik LLVM IR, termasuk definisi fungsi, instruksi
add, dan konvensi penamaan dengan prefiks%dan@. - LLVM — a compilation framework — LLVM Project — Halaman utama proyek LLVM dengan tautan ke arsitektur, pass, dan back end yang didukung.
- Writing an LLVM Compiler Front End — LLVM Project — Tutorial resmi yang menunjukkan langkah membangun front end bahasa baru yang menargetkan LLVM IR.
- Clang — a C language family frontend for LLVM — LLVM Project — Front end produksi untuk C dan C++ yang mengilustrasikan pola front end ke LLVM IR pada skala industri.