JavaScriptCore Derleme Pipeline'ı
Bugün uzun zamandır yapmak istediğim bir işe giriştim ve WebKit Javascript dilini nasıl tokenize edip, IR çıkarıyor diye code-review yapmaya başladım. Burayı etüt ettikçe güncelleyeceğim fakat, ilk pipeline olarak aşağıdaki resim çıkmaktadır.
JavaScript kaynak kodunun çalıştırılabilir makine koduna dönüşene kadar geçtiği tüm aşamalar.
Genel Akış
JavaScript Kaynak Kodu ("var x = 1 + 2;")
│
▼
┌───────────────┐
│ 1. LEXER │ parser/Lexer.h, Lexer.cpp
│ (Tokenizer) │ → Kaynak metni token'lara ayırır
│ │ → JSToken: IDENT, NUMBER, STRING, PLUS, SEMICOLON...
└───────┬───────┘
▼
┌───────────────┐
│ 2. PARSER │ parser/Parser.h, Parser.cpp
│ │ → Token akışından AST (Soyut Sözdizim Ağacı) üretir
│ │ → AST düğümleri: parser/Nodes.h
│ │ (ExpressionNode, FunctionNode, AssignmentNode vs.)
│ │ → ASTBuilder.h ile düğümler inşa edilir
└───────┬───────┘
▼
┌───────────────┐
│ 3. BYTECODE │ bytecompiler/BytecodeGenerator.h
│ GENERATOR │ bytecompiler/NodesCodegen.cpp
│ │ → AST üzerinde yürüyerek bytecode üretir
│ │ → Her AST düğümünün emitBytecode() metodu var
│ │ → Çıktı: UnlinkedCodeBlock (bytecode/UnlinkedCodeBlock.h)
└───────┬───────┘
▼
┌───────────────┐
│ 4. LLInt │ llint/LowLevelInterpreter.asm
│ (Yorumlayıcı) │ llint/LLIntSlowPaths.cpp
│ │ → Bytecode'u doğrudan yorumlar (interpret eder)
│ │ → Assembly ile yazılmış, çok hızlı dispatch
│ │ → Her fonksiyon ilk burada çalışır
│ │ → Çalışma sayacı tutar → eşik aşılınca üst katmana geçiş
└───────┬───────┘
▼ (fonksiyon "sıcak" olunca)
┌───────────────┐
│ 5. BASELINE │ jit/JIT.h, JIT.cpp
│ JIT │ → Bytecode'u bire bir native koda çevirir
│ │ → Hızlı derleme, basit optimizasyon
│ │ → Hâlâ çalışma sayacı tutuyor...
└───────┬───────┘
▼ (daha da sıcak olunca)
┌───────────────┐
│ 6. DFG JIT │ dfg/DFGGraph.h, dfg/DFGByteCodeParser.cpp
│ (Data Flow │ → Bytecode'u SSA benzeri DFG IR'a çevirir
│ Graph) │ → Tip çıkarımı, satır içi açma (inlining), sabit katlama
│ │ → Spekülatif optimizasyon (tip kontrolleri ile)
│ │ → OSR Exit: spekülasyon yanlışsa LLInt'e geri döner
└───────┬───────┘
▼ (en sıcak kodlar)
┌───────────────┐
│ 7. FTL JIT │ ftl/FTLLowerDFGToB3.cpp
│ (Faster Than │ ftl/FTLCompile.cpp
│ Light) │ → DFG IR'ı B3 IR'a (b3/ dizini) dönüştürür
│ │ → B3 = Bare Bones Backend (kendi derleyici arka ucu)
│ │ → En agresif optimizasyonlar burada yapılır
│ │ → Yazmaç tahsisi, komut seçimi vs.
└───────────────┘
Aşamalar Detaylı
1. Lexer (Sözcüksel Çözümleyici)
Kaynak kodu karakter karakter okuyarak anlamlı token'lara ayırır.
- Dosyalar:
parser/Lexer.h,parser/Lexer.cpp - Girdi: Ham JavaScript kaynak metni
- Çıktı: Token akışı (JSToken)
- Sınıf:
Lexer<T>— template parametreLChar(Latin1) veyaUChar(Unicode) olabilir - Ana metot:
lex()— bir sonraki token'ı üretir - Anahtar kelimeler:
Keywords.tabledosyasından üretilir
2. Parser (Sözdizim Çözümleyici)
Token akışını alıp AST (Abstract Syntax Tree / Soyut Sözdizim Ağacı) oluşturur.
- Dosyalar:
parser/Parser.h,parser/Parser.cpp - AST düğümleri:
parser/Nodes.h,parser/NodeConstructors.h - AST inşası:
parser/ASTBuilder.h - Çıktı:
ProgramNode,FunctionNode,ExpressionNodevs. - Önemli:
SyntaxChecker.h— tam AST oluşturmadan sadece sözdizimi doğrulaması yapar (lazy parsing için kullanılır)
Lazy parsing önemli bir optimizasyon: bir fonksiyon tanımlandığında hemen tamamen parse edilmez, sadece sözdizimi kontrol edilir. Fonksiyon ilk kez çağrıldığında tam parse yapılır.
3. Bytecode Generator (Bytecode Üretici)
AST ağacını yürüyerek düz bytecode talimatları üretir.
- Dosyalar:
bytecompiler/BytecodeGenerator.h,bytecompiler/BytecodeGenerator.cpp - Kod üretimi:
bytecompiler/NodesCodegen.cpp— her AST düğümü içinemitBytecode()implementasyonu - Çıktı:
UnlinkedCodeBlock→ sonra link edilerekCodeBlockolur - Yazmaç tabanlı: Yığın (stack) tabanlı değil, yazmaç (register) tabanlı bytecode kullanır
- Bytecode tanımları:
bytecode/BytecodeList.rbveyabytecode/BytecodeStructs.h
4. LLInt (Low Level Interpreter / Düşük Seviye Yorumlayıcı)
Bytecode'u doğrudan yorumlayan ilk çalıştırma katmanı.
- Dosyalar:
llint/LowLevelInterpreter.asm(ana döngü),llint/LowLevelInterpreter64.asm(64-bit platforma özel) - Yavaş yollar:
llint/LLIntSlowPaths.cpp— karmaşık işlemler için C++ geri dönüşleri - Giriş noktası:
llint/LLIntEntrypoint.cpp - Neden assembly? Performans. Her bytecode talimatı için doğrudan atlama (direct threading) kullanır
- Profilleme: Hangi tiplerin kullanıldığını, hangi dalların alındığını kaydeder
5. Baseline JIT (Temel JIT Derleyici)
Bytecode'u bire bir (1:1) native makine koduna çevirir, fazla optimizasyon yapmadan.
- Dosyalar:
jit/JIT.h,jit/JIT.cpp - Opcode işleyiciler:
jit/JITOpcodes.cpp - Yardımcı işlemler:
jit/JITOperations.cpp - Yaklaşım: Her bytecode talimatı için karşılık gelen makine kodu üretir
- Avantaj: Hızlı derleme süresi, LLInt'ten belirgin şekilde hızlı çalıştırma
- Profilleme devam eder: Tip bilgileri ve dal istatistikleri toplanmaya devam eder
6. DFG JIT (Data Flow Graph / Veri Akış Çizgesi)
Spekülatif optimizasyonlar yapan orta seviye JIT derleyici.
- Dizin:
dfg/ - Bytecode → DFG:
dfg/DFGByteCodeParser.cpp - Çizge yapısı:
dfg/DFGGraph.h,dfg/DFGNode.h - Optimizasyon geçişleri:
- Sabit katlama (constant folding)
- Tip çıkarımı (type inference)
- Satır içi açma (inlining)
- Ortak alt ifade eleme (CSE)
- Ölü kod eleme (DCE)
- Spekülatif: Profilleme verisine dayanarak varsayımlar yapar (ör. "bu değişken hep integer")
- OSR Exit: Varsayım yanlış çıkarsa çalışma anında LLInt/Baseline'a geri döner
7. FTL JIT (Faster Than Light)
En agresif optimizasyonları yapan en üst katman derleyici.
- Dosyalar:
ftl/FTLLowerDFGToB3.cpp(DFG → B3 dönüşümü),ftl/FTLCompile.cpp - B3 (Bare Bones Backend):
b3/dizininde bulunan, JSC'nin kendi alt seviye derleyici altyapısı- Eskiden LLVM kullanılıyordu, sonra B3 ile değiştirildi
- B3 → Air (Assembly IR) → makine kodu
- Air:
b3/air/— yazmaç tahsisi ve komut seçimi burada yapılır - Optimizasyonlar: B3 seviyesinde döngü optimizasyonu, gelişmiş yazmaç tahsisi ve daha fazlası
Katman Geçiş (Tier-Up) Mekanizması
Her CodeBlock bir çalışma sayacı (execution counter) tutar:
| Geçiş | Tetikleyici |
|---|---|
| LLInt → Baseline | Fonksiyon N kez çağrıldığında veya döngü N kez döndüğünde |
| Baseline → DFG | Daha fazla çalışma + yeterli profilleme verisi toplandığında |
| DFG → FTL | Kod hâlâ çok sıcaksa ve FTL'in kaldırabileceği yapıdaysa |
OSR (On-Stack Replacement): Bir fonksiyon çalışırken bile üst katmana geçiş yapabilir. Örneğin uzun bir döngünün ortasında Baseline'dan DFG'ye atlanabilir. Fonksiyonun bitmesini beklemeye gerek yoktur.
OSR Exit: Tam tersi yönde de çalışır. DFG spekülatif bir varsayım yaptıysa ve çalışma anında bu varsayım yanlış çıkarsa, çalışma anında alt katmana (LLInt veya Baseline) geri dönülür.
Özet Tablosu
| Aşama | Girdi | Çıktı | Temel Dosyalar |
|---|---|---|---|
| Lexer | Kaynak metin | Token akışı | parser/Lexer.h |
| Parser | Token'lar | AST (Nodes) | parser/Parser.h, parser/Nodes.h |
| BytecodeGen | AST | Bytecode | bytecompiler/BytecodeGenerator.h |
| LLInt | Bytecode | Yorumlama | llint/LowLevelInterpreter.asm |
| Baseline JIT | Bytecode | Native kod | jit/JIT.h |
| DFG JIT | Bytecode + Profil | Optimized native | dfg/ |
| FTL JIT | DFG IR → B3 IR | Çok optimized native | ftl/, b3/ |
V8 ile Karşılaştırma
| JSC | V8 | Rol |
|---|---|---|
| LLInt | Ignition | Bytecode yorumlayıcı |
| Baseline JIT | Sparkplug | Basit JIT derleyici |
| DFG | Maglev | Orta seviye optimizasyon |
| FTL | TurboFan | En üst seviye optimizasyon |
| B3 | TurboFan IR | Derleyici arka uç IR'ı |