Apa itu LLVM? Kekuatan di balik Swift, Rust, Clang, dan lainnya

Bahasa baru, dan perbaikan dari yang sudah ada, menjamur di seluruh lanskap develoment. Mozilla's Rust, Apple's Swift, Jetbrains's Kotlin, dan banyak bahasa lainnya memberi pengembang berbagai pilihan baru untuk kecepatan, keamanan, kenyamanan, portabilitas, dan kekuatan.

Kenapa sekarang? Salah satu alasan besarnya adalah alat baru untuk membangun bahasa — khususnya, kompiler. Dan yang paling utama di antaranya adalah LLVM, proyek sumber terbuka yang awalnya dikembangkan oleh pencipta bahasa Swift Chris Lattner sebagai proyek penelitian di University of Illinois.

LLVM mempermudah untuk tidak hanya membuat bahasa baru, tetapi juga untuk meningkatkan pengembangan bahasa yang sudah ada. Ini menyediakan alat untuk mengotomatiskan banyak bagian yang paling tanpa pamrih dari tugas pembuatan bahasa: membuat kompiler, mem-porting kode yang dikeluarkan ke berbagai platform dan arsitektur, menghasilkan pengoptimalan khusus arsitektur seperti vektorisasi, dan menulis kode untuk menangani metafora bahasa umum seperti pengecualian. Pemberian lisensi liberal berarti dapat digunakan kembali secara bebas sebagai komponen perangkat lunak atau digunakan sebagai layanan.

Daftar bahasa yang menggunakan LLVM memiliki banyak nama akrab. Bahasa Swift Apple menggunakan LLVM sebagai kerangka kerja kompilernya, dan Rust menggunakan LLVM sebagai komponen inti dari rantai alatnya. Selain itu, banyak kompiler memiliki edisi LLVM, seperti Clang, kompiler C / C ++ (ini namanya, "C-lang"), itu sendiri adalah proyek yang terkait erat dengan LLVM. Mono, implementasi .NET, memiliki opsi untuk mengkompilasi ke kode asli menggunakan ujung belakang LLVM. Dan Kotlin, yang secara nominal merupakan bahasa JVM, sedang mengembangkan versi bahasa yang disebut Kotlin Native yang menggunakan LLVM untuk mengompilasi ke kode asli mesin.

LLVM ditentukan

Intinya, LLVM adalah pustaka untuk membuat kode asli mesin secara terprogram. Pengembang menggunakan API untuk menghasilkan instruksi dalam format yang disebut representasi perantara , atau IR. LLVM kemudian dapat mengkompilasi IR menjadi biner mandiri atau melakukan kompilasi JIT (just-in-time) pada kode untuk dijalankan dalam konteks program lain, seperti juru bahasa atau runtime untuk bahasa tersebut.

API LLVM menyediakan primitif untuk mengembangkan banyak struktur dan pola umum yang ditemukan dalam bahasa pemrograman. Misalnya, hampir setiap bahasa memiliki konsep fungsi dan variabel global, dan banyak yang memiliki antarmuka coroutine dan fungsi asing C. LLVM memiliki fungsi dan variabel global sebagai elemen standar dalam IR-nya, dan memiliki metafora untuk membuat coroutine dan berinteraksi dengan pustaka C.

Alih-alih menghabiskan waktu dan energi untuk menciptakan kembali roda tertentu itu, Anda dapat menggunakan implementasi LLVM dan fokus pada bagian bahasa Anda yang membutuhkan perhatian.

Baca selengkapnya tentang Go, Kotlin, Python, dan Rust 

Pergilah:

  • Ketuk kekuatan bahasa Go Google
  • IDE dan editor bahasa Go terbaik

Kotlin:

  • Apa Kotlin itu? Alternatif Jawa dijelaskan
  • Kerangka kerja Kotlin: Survei alat pengembangan JVM

Python:

  • Apa itu Python? Semua yang perlu Anda ketahui
  • Tutorial: Bagaimana memulai dengan Python
  • 6 perpustakaan penting untuk setiap pengembang Python

Karat:

  • Apa itu Rust? Cara melakukan pengembangan perangkat lunak yang aman, cepat, dan mudah
  • Pelajari cara memulai Rust 

LLVM: Dirancang untuk portabilitas

Untuk memahami LLVM, mungkin membantu untuk mempertimbangkan analogi dengan bahasa pemrograman C: C kadang-kadang digambarkan sebagai bahasa rakitan tingkat tinggi portabel, karena memiliki konstruksi yang dapat memetakan secara dekat ke perangkat keras sistem, dan telah di-porting ke hampir setiap arsitektur sistem. Tetapi C berguna sebagai bahasa rakitan portabel hanya sampai titik tertentu; itu tidak dirancang untuk tujuan tertentu.

Sebaliknya, IR LLVM dirancang dari awal menjadi rakitan portabel. Salah satu cara untuk menyelesaikan portabilitas ini adalah dengan menawarkan primitif yang tidak bergantung pada arsitektur mesin tertentu. Misalnya, jenis bilangan bulat tidak terbatas pada lebar bit maksimum perangkat keras yang mendasarinya (seperti 32 atau 64 bit). Anda dapat membuat tipe integer primitif menggunakan bit sebanyak yang diperlukan, seperti integer 128-bit. Anda juga tidak perlu khawatir tentang membuat keluaran agar sesuai dengan set instruksi prosesor tertentu; LLVM akan mengurusnya untuk Anda juga.

Desain arsitektur-netral LLVM membuatnya lebih mudah untuk mendukung semua jenis perangkat keras, sekarang dan masa depan. Misalnya, IBM baru-baru ini menyumbangkan kode untuk mendukung z / OS, Linux on Power (termasuk dukungan untuk pustaka vektorisasi MASS IBM), dan arsitektur AIX untuk proyek LLVM C, C ++, dan Fortran. 

Jika Anda ingin melihat contoh langsung LLVM IR, buka situs web Proyek ELLCC dan coba demo langsung yang mengubah kode C menjadi LLVM IR langsung di browser.

Bagaimana bahasa pemrograman menggunakan LLVM

Kasus penggunaan paling umum untuk LLVM adalah sebagai kompiler di depan waktu (AOT) untuk suatu bahasa. Misalnya, project Clang sebelumnya mengompilasi C dan C ++ ke biner native. Tapi LLVM memungkinkan hal lain juga.

Kompilasi just-in-time dengan LLVM

Beberapa situasi memerlukan kode untuk dibuat dengan cepat saat runtime, daripada dikompilasi sebelumnya. Bahasa Julia, misalnya, JIT-mengkompilasi kodenya, karena ia perlu berjalan cepat dan berinteraksi dengan pengguna melalui REPL (read-eval-print loop) atau prompt interaktif. 

Numba, paket akselerasi matematika untuk Python, JIT mengompilasi fungsi Python yang dipilih ke kode mesin. Itu juga dapat mengkompilasi kode yang dihias Numba sebelumnya, tetapi (seperti Julia) Python menawarkan pengembangan cepat dengan menjadi bahasa yang ditafsirkan. Menggunakan kompilasi JIT untuk menghasilkan kode seperti itu melengkapi alur kerja interaktif Python dengan lebih baik daripada kompilasi sebelumnya.

Yang lain sedang bereksperimen dengan cara baru untuk menggunakan LLVM sebagai JIT, seperti menyusun kueri PostgreSQL, yang menghasilkan peningkatan kinerja hingga lima kali lipat.

Optimasi kode otomatis dengan LLVM

LLVM tidak hanya mengkompilasi IR ke kode mesin asli. Anda juga dapat mengarahkannya secara terprogram untuk mengoptimalkan kode dengan tingkat perincian yang tinggi, sepanjang proses penautan. Pengoptimalan bisa sangat agresif, termasuk hal-hal seperti fungsi sebaris, menghilangkan kode mati (termasuk deklarasi tipe dan argumen fungsi yang tidak digunakan), dan membuka gulungan loop.

Sekali lagi, kekuatannya adalah tidak harus menerapkan semua ini sendiri. LLVM dapat menanganinya untuk Anda, atau Anda dapat mengarahkannya untuk menonaktifkannya sesuai kebutuhan. Misalnya, jika Anda menginginkan biner yang lebih kecil dengan mengorbankan beberapa performa, Anda dapat meminta front end compiler Anda memberi tahu LLVM untuk menonaktifkan loop unrolling.

Bahasa khusus domain dengan LLVM

LLVM telah digunakan untuk menghasilkan kompiler untuk banyak bahasa tujuan umum, tetapi juga berguna untuk menghasilkan bahasa yang sangat vertikal atau eksklusif untuk domain masalah. Dalam beberapa hal, di sinilah LLVM paling bersinar, karena menghilangkan banyak pekerjaan membosankan dalam membuat bahasa seperti itu dan membuatnya bekerja dengan baik.

Proyek Emscripten, misalnya, mengambil kode LLVM IR dan mengubahnya menjadi JavaScript, secara teori mengizinkan bahasa apa pun dengan ujung belakang LLVM untuk mengekspor kode yang dapat berjalan di browser. Rencana jangka panjangnya adalah memiliki ujung belakang berbasis LLVM yang dapat menghasilkan WebAssembly, tetapi Emscripten adalah contoh yang baik tentang betapa fleksibelnya LLVM.

Cara lain LLVM dapat digunakan adalah dengan menambahkan ekstensi khusus domain ke bahasa yang sudah ada. Nvidia menggunakan LLVM untuk membuat Nvidia CUDA Compiler, yang memungkinkan bahasa menambahkan dukungan asli untuk CUDA yang dikompilasi sebagai bagian dari kode asli yang Anda buat (lebih cepat), alih-alih dipanggil melalui pustaka yang dikirimkan bersamanya (lebih lambat).

Keberhasilan LLVM dengan bahasa khusus domain telah mendorong proyek baru dalam LLVM untuk mengatasi masalah yang mereka buat. Masalah terbesar adalah bagaimana beberapa DSL sulit diterjemahkan ke dalam LLVM IR tanpa banyak kerja keras di bagian depannya. Salah satu solusi yang sedang dikerjakan adalah Multi-Level Intermediate Representation, atau proyek MLIR.

MLIR menyediakan cara mudah untuk merepresentasikan struktur dan operasi data yang kompleks, yang kemudian dapat diterjemahkan secara otomatis ke LLVM IR. Misalnya, framework machine learning TensorFlow dapat memiliki banyak operasi grafik aliran data yang kompleks yang dikompilasi secara efisien ke kode native dengan MLIR.

Bekerja dengan LLVM dalam berbagai bahasa

Cara khas untuk bekerja dengan LLVM adalah melalui kode dalam bahasa yang Anda sukai (dan itu memiliki dukungan untuk perpustakaan LLVM, tentu saja).

Dua pilihan bahasa yang umum adalah C dan C ++. Banyak pengembang LLVM default ke salah satu dari dua alasan tersebut karena beberapa alasan bagus: 

  • LLVM sendiri ditulis dalam C ++.
  • API LLVM tersedia dalam inkarnasi C dan C ++.
  • Banyak perkembangan bahasa cenderung terjadi dengan C / C ++ sebagai basis

Meski begitu, kedua bahasa itu bukanlah satu-satunya pilihan. Banyak bahasa dapat memanggil secara native ke dalam perpustakaan C, jadi secara teori dimungkinkan untuk melakukan pengembangan LLVM dengan bahasa seperti itu. Tapi itu membantu untuk memiliki perpustakaan yang sebenarnya dalam bahasa yang dengan elegan membungkus API LLVM. Untungnya, banyak bahasa dan runtime bahasa memiliki pustaka seperti itu, termasuk C # /. NET / Mono, Rust, Haskell, OCAML, Node.js, Go, dan Python.

Satu peringatan adalah bahwa beberapa pengikatan bahasa ke LLVM mungkin kurang lengkap dibandingkan yang lain. Dengan Python, misalnya, ada banyak pilihan, tetapi masing-masing berbeda dalam kelengkapan dan kegunaannya:

  • llvmlite, dikembangkan oleh tim yang menciptakan Numba, telah muncul sebagai pesaing saat ini untuk bekerja dengan LLVM dengan Python. Ini hanya mengimplementasikan sebagian dari fungsi LLVM, seperti yang ditentukan oleh kebutuhan proyek Numba. Tetapi subset itu menyediakan sebagian besar dari apa yang dibutuhkan pengguna LLVM. (llvmlite umumnya merupakan pilihan terbaik untuk bekerja dengan LLVM dengan Python.)
  • Proyek LLVM mempertahankan kumpulan bindingnya sendiri ke C API LLVM, tetapi saat ini tidak dipertahankan.
  • llvmpy, penjilidan Python populer pertama untuk LLVM, tidak lagi dalam pemeliharaan pada tahun 2015. Buruk untuk proyek perangkat lunak mana pun, tetapi lebih buruk lagi saat bekerja dengan LLVM, mengingat jumlah perubahan yang terjadi di setiap edisi LLVM.
  • llvmcpy bertujuan untuk memperbarui binding Python untuk library C, memperbaruinya dengan cara otomatis, dan membuatnya dapat diakses menggunakan idiom native Python. llvmcpy masih dalam tahap awal, tetapi sudah dapat melakukan beberapa pekerjaan dasar dengan API LLVM.

Jika Anda penasaran tentang bagaimana menggunakan perpustakaan LLVM untuk membangun bahasa, pembuat LLVM sendiri memiliki tutorial, baik menggunakan C ++ atau OCAML, yang memandu Anda melalui pembuatan bahasa sederhana yang disebut Kaleidoskop. Sejak itu telah diporting ke bahasa lain:

  • Haskell:  Port langsung dari tutorial asli.
  • Python: Salah satu port tersebut mengikuti tutorial dengan saksama, sementara yang lain adalah penulisan ulang yang lebih ambisius dengan baris perintah interaktif. Keduanya menggunakan llvmlite sebagai pengikat ke LLVM.
  • Rust  dan  Swift: Tampaknya tak terhindarkan kami akan mendapatkan port tutorial ke dua bahasa yang dibantu oleh LLVM.

Terakhir, tutorial juga tersedia dalam  bahasa manusia . Ini telah diterjemahkan ke dalam bahasa Cina, menggunakan C ++ dan Python asli.

Apa yang tidak dilakukan LLVM

Dengan semua yang LLVM berikan, ada gunanya juga mengetahui apa yang tidak dilakukannya.

Misalnya, LLVM tidak mengurai tata bahasa. Banyak alat sudah melakukan pekerjaan itu, seperti lex / yacc, flex / bison, Lark, dan ANTLR. Parsing dimaksudkan untuk dipisahkan dari kompilasi, jadi tidak mengherankan LLVM tidak mencoba mengatasi semua ini.

LLVM juga tidak secara langsung membahas budaya perangkat lunak yang lebih besar di sekitar bahasa tertentu. Menginstal binari kompiler, mengelola paket dalam instalasi, dan memutakhirkan rantai alat — Anda perlu melakukannya sendiri.

Akhirnya, dan yang paling penting, masih ada bagian umum bahasa yang tidak diberikan secara primitif oleh LLVM. Banyak bahasa memiliki beberapa cara pengelolaan memori yang dikumpulkan dari sampah, baik sebagai cara utama untuk mengelola memori atau sebagai tambahan untuk strategi seperti RAII (yang digunakan C ++ dan Rust). LLVM tidak memberi Anda mekanisme pengumpul sampah, tetapi menyediakan alat untuk mengimplementasikan pengumpulan sampah dengan mengizinkan kode ditandai dengan metadata yang membuat penulisan pengumpul sampah lebih mudah.

Namun, semua ini tidak mengesampingkan kemungkinan bahwa LLVM pada akhirnya dapat menambahkan mekanisme asli untuk menerapkan pengumpulan sampah. LLVM berkembang dengan cepat, dengan rilis utama setiap enam bulan atau lebih. Dan laju perkembangan kemungkinan hanya akan meningkat berkat cara banyak bahasa saat ini menempatkan LLVM sebagai jantung dari proses pengembangan mereka.