Menguasai JIT compilation LLVM: evolusi MCJIT → ORC v2 (On-Request Compilation), LLJIT high-level API, lazy re-export, dan practical use cases — embedded scripting engine, SQL query JIT, database UDF, game scripting — dengan praktik membangun runtime ekspresi matematis.

Setelah di episode 19 kita membahas linking dan LTO, pada episode ini kita membahas mode kompilasi yang berbeda: JIT (Just-In-Time) Compilation — mengonversi kode ke native code saat runtime, bukan saat build time.
JIT compilation LLVM memungkinkan kalian mengeksekusi kode LLVM IR secara langsung dari program kalian tanpa kompilasi ke file. Ini membuka use case yang menarik: scripting engine yang secepat native code, SQL query compiler yang mengoptimasi per-query, dan game scripting yang bisa dimodifikasi tanpa restart.
MCJIT adalah JIT compiler pertama LLVM — menggunakan MC Layer untuk codegen. Tetapi ia memiliki limitasi: tidak mendukung lazy compilation, tidak bisa recompile, dan API-nya cukup rendah-level.
ORC v2 adalah JIT framework modern yang menggantikan MCJIT:
| Fitur | MCJIT | ORC v2 |
|---|---|---|
| Lazy compilation | Tidak | Ya |
| Recompilation | Tidak | Ya |
| Thread-safe | Terbatas | Ya |
| High-level API | Tidak | LLJIT, LLLazyJIT |
| Symbol resolution | Statis | Dynamic (re-export) |
LLJIT adalah API tingkat tinggi yang menyederhanakan JIT compilation:
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/Support/Error.h"
using namespace llvm;
using namespace llvm::orc;
int main() {
// Buat LLJIT instance
auto JITBuilder = LLJITBuilder();
auto JIT = JITBuilder.create();
if (!JIT) {
errs() << "Failed to create LLJIT: " << toString(JIT.takeError());
return 1;
}
// Compile module
auto M = std::make_unique<Module>("expr", LLVMContext());
// ... build IR di M ...
(*JIT)->addIRModule(ThreadSafeModule(std::move(M),
std::make_unique<LLVMContext>()));
// Lookup dan eksekusi
auto Entry = (*JIT)->lookup("main");
if (!Entry) {
errs() << "Failed to find entry point";
return 1;
}
auto *Fn = Entry->toPtr<int()>();
int result = Fn();
outs() << "Result: " << result << "\n";
return 0;
}auto JITBuilder = LLJITBuilder();
JITBuilder.setCompileOnDemandLayerCreator(
[](ExecutionSession &ES, JITDylib &JD) {
return std::make_unique<CompileOnDemandLayer>(
ES, JD, std::make_unique<SimpleCompiler>(*TM));
});
auto JIT = JITBuilder.create();Lazy compilation menunda kompilasi fungsi hingga pertama kali dipanggil — mengurangi startup time secara drastis untuk program dengan banyak fungsi yang belum tentu dipakai.
Runtime yang mengkompilasi script user ke native code —seperti LuaJIT tetapi menggunakan LLVM IR:
void executeScript(const std::string &code) {
auto IR = parseAndCodegen(code);
JIT->addIRModule(std::move(IR));
auto Fn = JIT->lookup("main")->toPtr<void()>();
Fn();
}Database seperti MySQL 8.0 dan PostgreSQL menggunakan JIT untuk query kompleks — mengkompilasi query plan ke native code daripada mengevaluasi interpreter:
Value *compileExpression(const ExprAST *Expr, Function *QueryFn) {
IRBuilder<> Builder(QueryFn->getContext());
Builder.SetInsertPoint(&QueryFn->getEntryBlock());
return Expr->codegen(Builder);
}Game engine bisa mengkompilasi game logic ke native code saat level dimuat — perubahan script langsung terasa secepat native tanpa restart.
Tip
ORC JIT mendukung JIT compilation yang thread-safe. Dalam aplikasi production, satu LLJIT instance bisa melayani beberapa thread secara bersamaan — tetapi pastikan LLVMContext digunakan dengan benar untuk menghindari data races.
Bangun runtime yang mengkompilasi string ekspresi matematis ke native code saat request:
./jit_runtime "2 + 3 * 4"
=> 14
./jit_runtime "if x > 5 then x * 2 else x + 10"
=> (fungsi dengan parameter x)Kompilasi sekali saat string diterima, cache hasilnya, dan eksekusi native untuk request berikutnya.
Inti yang harus dibawa pulang:
Di episode 21 selanjutnya kita akan membahas sanitizers & instrumentation — AddressSanitizer, MemorySanitizer, ThreadSanitizer, UndefinedBehaviorSanitizer, SanitizerCoverage, libFuzzer, dan llvm-cov/source-based coverage. Sampai jumpa di episode 21!