Empat istilah sering muncul saat membahas penerjemah bahasa pemrograman: interpreter, compiler, transpiler, dan JIT. Topik ini memetakan keempatnya sebagai satu spektrum penerjemah dengan dua sumbu: bahasa sasaran dan waktu terjemahan. Pembaca memperoleh taksonomi yang konsisten untuk dipakai saat pilihan implementasi bahasa mini harus dibuat secara eksplisit.
Apa yang diterjemahkan dan ke mana#
Setiap penerjemah bahasa memiliki dua hal yang ditetapkan: bahasa sasaran (target) dan waktu terjemahan. Bahasa sasaran menentukan apa yang dihasilkan, yaitu kode mesin native, bytecode mesin virtual, atau bahasa tingkat tinggi lain. Waktu terjemahan menentukan kapan proses terjemahan terjadi, yaitu sebelum program dijalankan (ahead-of-time, AOT) atau saat program sedang berjalan (just-in-time, JIT).
Compiler adalah penerjemah yang menghasilkan keluaran berupa berkas atau struktur yang dapat disimpan. Keluaran itu dapat berupa kode mesin, bytecode, atau bahasa sumber lain. Interpreter adalah penerjemah yang langsung mengeksekusi kode tanpa menghasilkan berkas keluaran. Kedua istilah ini sering tumpang tindih di praktik: CPython mengompilasi sumber Python ke bytecode lalu menginterpretasi bytecode tersebut, sehingga dalam satu lingkungan terdapat kedua peran.
Empat pendekatan#
Perbandingan keempat pendekatan ditunjukkan pada tabel berikut. Kolom “Keluaran” menyatakan apa yang dihasilkan penerjemah, sedangkan kolom “Kapan dieksekusi” menyatakan titik di mana kode sasaran dijalankan.
| Pendekatan | Keluaran | Kapan dieksekusi | Contoh nyata |
|---|---|---|---|
| Interpreter | tidak ada berkas, eksekusi langsung | seketika saat sumber dibaca | shell bash, REPL Python yang mengevaluasi string |
| Compiler AOT | berkas kode mesin atau bytecode | setelah berkas ditulis dan dibuka kembali | gcc untuk C, rustc untuk Rust, javac untuk Java |
| Transpiler | berkas kode dalam bahasa tingkat tinggi lain | saat keluaran dijalankan runtime bahasa sasaran | TypeScript ke JavaScript oleh tsc, Babel |
| JIT | kode mesin di memori, dibangun saat runtime | seketika setelah kompilasi, untuk kode yang sering dipakai | V8 untuk JavaScript, JVM C2, PyPy |
Batas antar-pendekatan tidak selalu tegas. Beberapa compiler menghasilkan bytecode yang kemudian dijalankan oleh interpreter, dan beberapa interpreter mengompilasi kode panas ke kode mesin pada saat runtime. Yang penting bagi pembaca jalur ini adalah kesadaran bahwa pilihan tidak tunggal: compiler proyek kursus akan menargetkan stack VM, sehingga berperan sebagai compiler AOT yang menghasilkan bytecode, dengan runtime berperan sebagai interpreter atas bytecode tersebut.
Interpreter versus compiler dalam Python#
Python sendiri memperlihatkan kedua peran dalam satu bahasa. Fungsi bawaan eval menerima string ekspresi dan langsung mengembalikan hasilnya, sehingga dari sisi pengguna terlihat sebagai interpreter. Fungsi bawaan compile menerima string yang sama dan menghasilkan objek kode yang dapat dieksekusi kemudian dengan eval, sehingga dari sisi pengguna terlihat sebagai compiler dua tahap. Skrip berikut menjalankan kedua jalur pada ekspresi a + b * c dengan lingkungan yang sama, lalu memeriksa bytecode yang dihasilkan compiler.
"""Membandingkan dua jalur Python untuk menjalankan ekspresi string.
Jalur 1 (interpreter) memakai eval(sumber). Python mengurai sumber,
mengompilasi ke bytecode internal secara implisit, lalu mengeksekusi bytecode
itu pada mesin virtual CPython dalam satu panggilan.
Jalur 2 (compiler lalu eksekusi) memakai compile(sumber) -> objek kode, lalu
eval(objek_kode). Dua tahap eksplisit, dengan bytecode yang dapat diperiksa
melalui modul dis.
"""
import dis
sumber = "a + b * c"
env = {"a": 2, "b": 3, "c": 4}
# Jalur 1: interpreter (eval langsung)
hasil_eval = eval(sumber, {}, env)
print("jalur interpreter (eval) :", hasil_eval)
# Jalur 2: compiler menghasilkan objek kode, lalu interpreter mengeksekusi
objek_kode = compile(sumber, "<contoh>", "eval")
hasil_exec = eval(objek_kode, {}, env)
print("jalur compiler lalu eksekusi :", hasil_exec)
# Memeriksa keluaran compiler: daftar instruksi bytecode stack VM
print("bytecode yang dihasilkan compile :")
for ins in dis.get_instructions(objek_kode):
print(f" {ins.offset:3d} {ins.opname:<14} {ins.argrepr}")jalur interpreter (eval) : 14
jalur compiler lalu eksekusi : 14
bytecode yang dihasilkan compile :
0 RESUME
2 LOAD_NAME a
4 LOAD_NAME b
6 LOAD_NAME c
8 BINARY_OP *
12 BINARY_OP +
16 RETURN_VALUE Kedua jalur menghasilkan nilai yang sama, yaitu 14, sesuai dengan 2 + (3 * 4). Perbedaannya terletak pada titik di mana terjemahan terjadi. Pada jalur pertama, eval menyembunyikan langkah kompilasi ke bytecode internal, sehingga pengguna tidak melihat objek kode. Pada jalur kedua, compile memisahkan langkah kompilasi dari eksekusi, sehingga objek kode dapat diperiksa, disimpan, atau dijalankan ulang. Bytecode yang ditampilkan modul dis adalah deret instruksi stack VM: tiga LOAD_NAME mendorong nilai variabel ke stack, dua BINARY_OP melakukan operasi biner pada nilai di puncak stack, dan RETURN_VALUE mengembalikan hasil. Struktur ini persis sama dengan bytecode yang akan dibangkitkan compiler proyek kursus pada modul pembangkitan kode, hanya dengan nama instruksi yang berbeda.
Kesalahan umum#
Kesalahan konseptual yang sering muncul adalah menyamakan compiler dengan kode mesin. Compiler adalah penerjemah ke bahasa sasaran apa pun, termasuk bytecode mesin virtual. Kesalahan kedua adalah menganggap interpreter tidak pernah mengompilasi: mayoritas interpreter modern mengompilasi sumber ke bytecode internal sebelum mengeksekusi, karena pemindaian ulang string pada setiap iterasi terlalu lambat. Kesalahan ketiga adalah menyamakan JIT dengan compiler AOT: JIT mengompilasi saat runtime dan hanya untuk kode yang sering dipakai, sehingga keluarannya tidak pernah disimpan ke berkas.
Kesalahan keempat adalah menganggap transpiler berbeda secara mendasar dari compiler AOT. Transpiler hanyalah compiler yang bahasa sasarannya kebetulan berupa bahasa tingkat tinggi, sehingga aturan pipeline yang sama berlaku, mulai dari lexing hingga codegen. Perbedaan praktisnya terletak pada tahap codegen, yang menerjemahkan AST ke teks dalam bahasa sumber lain alih-alih ke kode mesin. Sebagai konsekuensi, transpiler sering memerlukan formatter sebagai tahap pasca-codegen agar keluaran dapat dibaca manusia.
Ringkasan#
- Empat pendekatan penerjemah dibedakan oleh bahasa sasaran dan waktu terjemahan, bukan oleh nama teknologi.
- Interpreter mengeksekusi langsung, compiler AOT menghasilkan berkas, transpiler menghasilkan bahasa tingkat tinggi lain, dan JIT mengompilasi saat runtime.
- Python memperlihatkan kedua peran:
evalsebagai jalur interpreter dancompilelaluevalsebagai jalur compiler dua tahap. - Compiler proyek kursus berperan sebagai compiler AOT yang menargetkan bytecode stack VM, dengan runtime berperan sebagai interpreter atas bytecode.
Referensi#
- Built-in Functions: compile — Python — Fungsi bawaan Python yang mengompilasi kode sumber menjadi objek kode untuk dieksekusi.
- Built-in Functions: eval — Python — Fungsi bawaan Python yang mengevaluasi ekspresi string sebagai interpreter.
- dis — Disassembler for Python bytecode — Python
— Modul yang memeriksa bytecode hasil
compile(), dipakai pada contoh perbandingan. - Just-in-time compilation — Wikipedia — Penjelasan konseptual JIT dan posisinya terhadap compiler AOT dan interpreter.