Membedah arsitektur internal Hermes: parser yang mengubah source menjadi AST, bytecode compiler yang menghasilkan HBC, Hades garbage collector generational, dan interpreter yang mengeksekusinya. Plus konsep bytecode serialization dan tiga runtime mode: full AOT, lazy compilation, dan inline caching.

Di episode 1 kita tahu Hermes unggul karena bytecode precompilation dan desain tanpa JIT. Sekarang kita masuk satu level lebih dalam: bagaimana Hermes bekerja dari dalam?
Episode 2 membedah alur pipeline kompilasi dan eksekusi: parser, bytecode compiler, garbage collector, dan interpreter. Lalu kita bahas format bytecode HBC beserta serialization-nya, dan tiga mode runtime yang membuat Hermes cepat.
Hermes bekerja dalam dua fase yang terpisah tegas: compile time dan runtime.
source.js --> Parser --> AST --> BytecodeCompiler --> .hbc (bytecode)
|
[compile time] v
[runtime] Interpreter
|
v
Garbage CollectorDi compile time, source dikompilasi sekali menjadi bytecode. Di runtime, Hermes tinggal mengeksekusi bytecode itu dengan interpreter — tanpa parsing dan tanpa JIT. Semua ini dilakukan oleh empat komponen utama.
Komponen pertama adalah parser. Tugasnya mengubah source code menjadi AST (Abstract Syntax Tree) — representasi hierarkis dari struktur kode. Parser Hermes dirancang untuk cepat dan hemat memori, karena di React Native parsing terjadi di build time, bukan saat user membuka app.
Parser juga melakukan semantic analysis ringan: mendeteksi error sintaks, menyimpan lokasi baris untuk source maps, dan menyiapkan data yang dibutuhkan compiler. Di Hermes, hasil parsing langsung dialirkan ke bytecode compiler tanpa harus menyimpan AST utuh di memori untuk waktu lama.
Komponen kedua adalah bytecode compiler (yang hidup di binary hermesc). Ia menerima AST dan menghasilkan bytecode — instruksi sederhana yang dikonsumsi interpreter. Tidak seperti JIT yang menghasilkan machine code per arsitektur CPU, bytecode Hermes bersifat portable: satu file .hbc bisa dijalankan di ARM dan x86.
Compiler juga melakukan optimisasi seperti static property resolution: nama property seperti obj.length di-resolve di compile time, jadi interpreter tidak perlu pencarian hash saat runtime.
Komponen ketiga adalah garbage collector (GC), yang di Hermes modern disebut Hades. Hades adalah GC generational dan concurrent:
Hasilnya: pause time rendah dan dapat diprediksi — penting untuk UI yang harus tetap responsif. Di episode 6 kita akan membedah GC lebih dalam.
Komponen keempat adalah interpreter. Ia membaca instruksi bytecode satu per satu dan mengeksekusinya: operasi aritmatika, pemanggilan fungsi, akses property, dan kontrol alur. Interpreter Hermes berbasis register machine, bukan stack machine — setiap fungsi punya slot register lokal, yang membuat eksekusi lebih cepat dan kode byte lebih ringkas.
Karena interpreter murni tanpa JIT, performanya memang tidak menyaingi V8 untuk compute-heavy. Namun untuk workload mobile, keuntungan determinisme dan memori jauh lebih berharga.
Pola ini membuat Hermes unik di antara engine modern: tanpa JIT, tapi dengan bytecode yang sudah dioptimalkan di compile time. Hasilnya perilaku runtime bisa diprediksi — tidak ada pemanasan bertahap yang menurunkan performa di detik-detik pertama seperti di engine ber-JIT.
Bytecode Hermes disimpan dalam format HBC (Hermes Bytecode). File ini adalah hasil serialization — struktur bytecode yang hidup di memori ditulis menjadi stream bytes yang bisa disimpan atau dikirim. Serialization ini membawa konsekuensi penting:
Lihat output bytecode sebuah file sederhana dengan flag debug:
echo "const a = 1; print(a + 2);" > test.js
build/bin/hermesc -dump-bytecode test.jsKeluaran -dump-bytecode memperlihatkan instruksi seperti LoadConstInt, Add, dan Call — bahasa mesin ringkas yang dimengerti interpreter.
Satu catatan: bytecode ini tidak ramah mata manusia — tidak ada nama variabel, yang ada hanya lokasi register dan konstanta. Karena itu debugging kode Hermes biasanya memakai source map, bukan membaca bytecode langsung.
Mode pertama adalah full AOT (Ahead-Of-Time). Di mode ini, seluruh source dikompilasi menjadi bytecode di build time, dan runtime hanya mengeksekusi file .hbc. Ini default di React Native: bundle JavaScript dikompilasi hermesc sebelum masuk ke APK. Keunggulannya, startup minimal — tidak ada pekerjaan parse sama sekali saat app diluncurkan.
Mode kedua adalah lazy compilation. Hermes tidak selalu mengkompilasi seluruh fungsi sekaligus. Fungsi besar yang belum dipanggil bisa ditunda kompilasinya sampai benar-benar dibutuhkan. Teknik ini mengurangi alokasi memori di awal dan mempercepat startup lebih jauh — kode yang tidak pernah dieksekusi tidak perlu pernah dikompilasi. Trade-off-nya, pemanggilan pertama fungsi tersebut sedikit lebih lambat.
Mode ketiga adalah inline caching. Interpreter menyimpan hasil lookup property pada lokasi akses tertentu. Jika user.name diakses di satu titik berulang kali, interpreter mengingat lokasi property tersebut dari panggilan pertama, sehingga akses berikutnya tidak mengulang pencarian dari awal. Ini kompensasi utama karena Hermes tidak punya JIT — optimisasi dinamis kecil dilakukan interpreter tanpa mengubah kode mesin.
// Jika shape user konsisten, akses user.name jadi murah
// setelah panggilan pertama karena di-cache interpreter.
function getNames(users) {
const names = [];
for (const user of users) {
names.push(user.name);
}
return names;
}Ingat satu syarat: shape object yang konsisten. Jika user berubah bentuk tiap iterasi (polimorfisme runtime), cache tidak bisa dipakai dan performa turun — kita bahas di episode 15.
Arsitektur Hermes kini jelas di kepala kalian. Inilah yang wajib kalian bawa pulang:
Selanjutnya, di episode 3 kita praktik: menambahkan Hermes ke project React Native, menyiapkan build environment Android/iOS, dan memverifikasi Hermes berjalan di emulator. Sampai jumpa!