Belajar Hermes JS Engine - Konsep Dasar & Arsitektur Utama
Episode 2 of 23

Belajar Hermes JS Engine - Konsep Dasar & Arsitektur Utama

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.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

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.

Alur Pipeline Hermes secara Keseluruhan

Hermes bekerja dalam dua fase yang terpisah tegas: compile time dan runtime.

Pipeline Hermes dari source ke eksekusi
source.js --> Parser --> AST --> BytecodeCompiler --> .hbc (bytecode)
                                                          |
                          [compile time]                   v
                          [runtime]                     Interpreter
                                                            |
                                                            v
                                                       Garbage Collector

Di 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.

Parser: dari Source Menjadi AST

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.

Bytecode Compiler: dari AST Menjadi HBC

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.

Hades Garbage Collector

Komponen ketiga adalah garbage collector (GC), yang di Hermes modern disebut Hades. Hades adalah GC generational dan concurrent:

  • Generational: object muda dan tua dirawat terpisah. Minor GC hanya menyapu object muda yang biasanya cepat mati — prosesnya ringan.
  • Concurrent: mayoritas kerja GC berjalan di thread latar belakang, sehingga app tidak berhenti total saat koleksi berlangsung.
  • Snapshot-at-the-beginning: teknik marking yang memastikan snapshot heap konsisten tanpa memblokir thread utama lama.

Hasilnya: pause time rendah dan dapat diprediksi — penting untuk UI yang harus tetap responsif. Di episode 6 kita akan membedah GC lebih dalam.

Interpreter: Penjahit Eksekusi

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 dan Serialization

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:

  • Portability: satu file HBC bisa dibagikan lintas arsitektur CPU.
  • Deterministic: output bytecode stabil untuk input yang sama, mendukung reproducible build.
  • Integrity: bytecode bisa ditandatangani, karena file sudah final di build time. Ini kita bahas di episode 13.

Lihat output bytecode sebuah file sederhana dengan flag debug:

Dump bytecode Hermes
echo "const a = 1; print(a + 2);" > test.js
build/bin/hermesc -dump-bytecode test.js

Keluaran -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 Runtime: Full AOT

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 Runtime: Lazy Compilation

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 Runtime: Inline Caching

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.

JSAkses property yang mendapat manfaat inline caching
// 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.

Penutup

Arsitektur Hermes kini jelas di kepala kalian. Inilah yang wajib kalian bawa pulang:

  • Pipeline Hermes: parser → bytecode compiler → interpreter, dengan GC Hades berjalan di sisi eksekusi.
  • Parser dan compiler bekerja di build time; interpreter hanya mengeksekusi bytecode di runtime.
  • Hades adalah GC generational dan concurrent yang menjaga pause time tetap rendah.
  • Bytecode HBC adalah format ter-serialize yang portable, deterministik, dan siap ditandatangani.
  • Tiga mode runtime: full AOT untuk startup tercepat, lazy compilation untuk menghemat alokasi awal, dan inline caching untuk mempercepat akses property berulang.

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!