Tampaknya ada setidaknya dua pertanyaan berbeda di sini. Salah satunya adalah tentang kompiler secara umum, dengan Java pada dasarnya hanya contoh dari genre. Yang lain lebih spesifik ke Jawa kode byte spesifik yang digunakannya.
Kompiler pada umumnya
Pertama-tama mari kita perhatikan pertanyaan umum: mengapa kompiler menggunakan beberapa representasi perantara dalam proses mengkompilasi kode sumber untuk dijalankan pada beberapa prosesor tertentu?
Pengurangan Kompleksitas
Satu jawaban untuk itu cukup sederhana: itu mengubah masalah O (N * M) menjadi masalah O (N + M).
Jika kita diberi bahasa sumber N, dan target M, dan setiap kompiler sepenuhnya independen, maka kita membutuhkan kompiler N * M untuk menerjemahkan semua bahasa sumber ke semua target tersebut (di mana "target" adalah sesuatu seperti kombinasi dari sebuah prosesor dan OS).
Namun, jika semua kompiler setuju pada representasi perantara umum, maka kita dapat memiliki ujung depan kompilator N yang menerjemahkan bahasa sumber ke representasi perantara, dan ujung kompilator M yang menerjemahkan representasi perantara ke sesuatu yang cocok untuk target tertentu.
Segmentasi Masalah
Lebih baik lagi, ini memisahkan masalah menjadi dua domain eksklusif lebih atau kurang. Orang yang tahu / peduli tentang desain bahasa, penguraian dan hal-hal seperti itu dapat berkonsentrasi pada ujung depan kompiler, sedangkan orang yang tahu tentang set instruksi, desain prosesor, dan hal-hal seperti itu dapat berkonsentrasi pada bagian belakang.
Jadi, misalnya, mengingat sesuatu seperti LLVM, kami memiliki banyak ujung depan untuk berbagai bahasa yang berbeda. Kami juga memiliki back-end untuk banyak prosesor yang berbeda. Seorang pria bahasa dapat menulis front-end baru untuk bahasanya, dan dengan cepat mendukung banyak target. Seorang pria prosesor dapat menulis back-end baru untuk targetnya tanpa berurusan dengan desain bahasa, penguraian, dll.
Memisahkan kompiler menjadi ujung depan dan ujung belakang, dengan representasi perantara untuk berkomunikasi antara keduanya tidak asli dengan Java. Sudah praktik yang cukup umum untuk waktu yang lama (sejak jauh sebelum Jawa datang, toh).
Model Distribusi
Sejauh Java menambahkan sesuatu yang baru dalam hal ini, itu dalam model distribusi. Secara khusus, meskipun kompiler telah dipisahkan menjadi bagian ujung depan dan ujung belakang secara internal untuk waktu yang lama, mereka biasanya didistribusikan sebagai produk tunggal. Misalnya, jika Anda membeli kompiler Microsoft C, secara internal ia memiliki "C1" dan "C2", yang merupakan front-end dan back-end - tetapi apa yang Anda beli hanyalah "Microsoft C" yang mencakup keduanya potongan (dengan "driver kompiler" yang mengoordinasikan operasi antara keduanya). Meskipun kompiler dibangun dalam dua bagian, untuk pengembang normal yang menggunakan kompiler itu hanya satu hal yang diterjemahkan dari kode sumber ke kode objek, dengan tidak ada yang terlihat di antaranya.
Java, sebaliknya, mendistribusikan front-end dalam Java Development Kit, dan back-end di Java Virtual Machine. Setiap pengguna Java memiliki back-end kompiler untuk menargetkan sistem apa pun yang ia gunakan. Pengembang Java mendistribusikan kode dalam format perantara, jadi ketika pengguna memuatnya, JVM melakukan apa pun yang diperlukan untuk menjalankannya pada mesin khusus mereka.
Preseden
Perhatikan bahwa model distribusi ini juga tidak sepenuhnya baru. Sebagai contoh, sistem-P UCSD bekerja dengan cara yang sama: ujung-ujung depan kompiler menghasilkan kode-P, dan setiap salinan sistem-P menyertakan mesin virtual yang melakukan apa yang diperlukan untuk mengeksekusi kode-P pada target tertentu 1 .
Kode byte Java
Kode byte Java sangat mirip dengan kode-P. Ini pada dasarnya instruksi untuk mesin yang cukup sederhana. Mesin itu dimaksudkan sebagai abstraksi dari mesin yang ada, sehingga cukup mudah untuk menerjemahkan dengan cepat ke hampir semua target spesifik. Kemudahan terjemahan adalah penting sejak awal karena maksud aslinya adalah untuk menafsirkan kode byte, seperti yang telah dilakukan P-System (dan, ya, itulah cara implementasi awal bekerja).
Kekuatan
Kode byte Java mudah untuk dihasilkan oleh front-end kompiler. Jika (misalnya) Anda memiliki pohon yang cukup khas mewakili ekspresi, biasanya cukup mudah untuk melintasi pohon, dan menghasilkan kode secara langsung dari apa yang Anda temukan di setiap node.
Kode byte Java cukup kompak - dalam banyak kasus, jauh lebih kompak daripada kode sumber atau kode mesin untuk prosesor yang paling khas (dan, terutama untuk sebagian besar prosesor RISC, seperti SPARC yang dijual Sun ketika mereka mendesain Java). Ini sangat penting pada saat itu, karena salah satu tujuan utama Java adalah untuk mendukung applet - kode yang tertanam di halaman web yang akan diunduh sebelum eksekusi - pada saat kebanyakan orang mengakses kami melalui modem melalui saluran telepon di sekitar 28,8 kilobit per detik (meskipun, tentu saja, masih ada beberapa orang yang menggunakan modem yang lebih tua dan lebih lambat).
Kelemahan
Kelemahan utama dari kode byte Java adalah bahwa mereka tidak terlalu ekspresif. Meskipun mereka dapat mengekspresikan konsep yang ada di Jawa dengan cukup baik, mereka tidak bekerja dengan baik untuk mengekspresikan konsep yang bukan bagian dari Jawa. Demikian juga, walaupun mudah untuk mengeksekusi kode byte pada kebanyakan mesin, jauh lebih sulit untuk itu dengan cara yang mengambil keuntungan penuh dari mesin tertentu.
Sebagai contoh, itu cukup rutin bahwa jika Anda benar-benar ingin mengoptimalkan kode byte Java, Anda pada dasarnya melakukan beberapa teknik reverse untuk menerjemahkannya kembali dari representasi seperti mesin-kode, dan mengubahnya kembali menjadi instruksi SSA (atau yang serupa) 2 . Anda kemudian memanipulasi instruksi SSA untuk melakukan optimasi Anda, kemudian menerjemahkan dari sana ke sesuatu yang menargetkan arsitektur yang benar-benar Anda pedulikan. Walaupun dengan proses yang agak rumit ini, bagaimanapun, beberapa konsep yang asing ke Jawa cukup sulit untuk diutarakan bahwa sulit untuk menerjemahkan dari beberapa bahasa sumber ke dalam kode mesin yang berjalan (bahkan hampir) secara optimal pada kebanyakan mesin yang khas.
Ringkasan
Jika Anda bertanya mengapa menggunakan representasi perantara secara umum, dua faktor utama adalah:
- Kurangi masalah O (N * M) menjadi masalah O (N + M), dan
- Pecahkan masalah menjadi beberapa bagian yang lebih mudah dikelola.
Jika Anda bertanya tentang spesifikasi kode byte Java, dan mengapa mereka memilih representasi khusus ini daripada yang lain, maka saya akan mengatakan jawabannya sebagian besar kembali ke maksud aslinya dan keterbatasan web pada saat itu. , yang mengarah ke prioritas berikut:
- Representasi yang kompak.
- Cepat dan mudah untuk memecahkan kode dan mengeksekusi.
- Cepat dan mudah diimplementasikan pada mesin yang paling umum.
Mampu mewakili banyak bahasa atau mengeksekusi secara optimal pada berbagai target adalah prioritas yang jauh lebih rendah (jika mereka dianggap prioritas sama sekali).
- Jadi mengapa sebagian besar sistem-P dilupakan? Sebagian besar situasi penetapan harga. Sistem-P dijual cukup baik di Apple II, Commodore SuperPets, dll. Ketika IBM PC keluar, sistem-P adalah OS yang didukung, tetapi biaya MS-DOS lebih sedikit (dari sudut pandang kebanyakan orang, pada dasarnya dilemparkan secara gratis) dan dengan cepat memiliki lebih banyak program yang tersedia, karena itulah yang ditulis untuk Microsoft dan IBM.
- Sebagai contoh, ini adalah cara kerja Soot .